├── .dockerignore ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml ├── licenses.tmpl ├── pull_request_template.md └── workflows │ ├── code-scanning.yml │ ├── docker-publish.yml │ ├── go.yml │ ├── goreleaser.yml │ ├── license-check.yml │ └── lint.yaml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yaml ├── .vscode └── launch.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── cmd ├── github-mcp-server │ └── main.go └── mcpcurl │ ├── README.md │ └── main.go ├── docs └── testing.md ├── e2e ├── README.md └── e2e_test.go ├── go.mod ├── go.sum ├── internal ├── ghmcp │ └── server.go ├── githubv4mock │ ├── githubv4mock.go │ ├── local_round_tripper.go │ ├── objects_are_equal_values.go │ ├── objects_are_equal_values_test.go │ └── query.go └── toolsnaps │ ├── toolsnaps.go │ └── toolsnaps_test.go ├── pkg ├── github │ ├── __toolsnaps__ │ │ └── get_me.snap │ ├── code_scanning.go │ ├── code_scanning_test.go │ ├── context_tools.go │ ├── context_tools_test.go │ ├── dynamic_tools.go │ ├── helper_test.go │ ├── issues.go │ ├── issues_test.go │ ├── notifications.go │ ├── notifications_test.go │ ├── pullrequests.go │ ├── pullrequests_test.go │ ├── repositories.go │ ├── repositories_test.go │ ├── repository_resource.go │ ├── repository_resource_test.go │ ├── resources.go │ ├── search.go │ ├── search_test.go │ ├── secret_scanning.go │ ├── secret_scanning_test.go │ ├── server.go │ ├── server_test.go │ └── tools.go ├── log │ ├── io.go │ └── io_test.go ├── toolsets │ ├── toolsets.go │ └── toolsets_test.go └── translations │ └── translations.go ├── script ├── get-me ├── licenses ├── licenses-check └── prettyprint-log ├── third-party-licenses.darwin.md ├── third-party-licenses.linux.md ├── third-party-licenses.windows.md └── third-party ├── github.com ├── fsnotify │ └── fsnotify │ │ └── LICENSE ├── github │ └── github-mcp-server │ │ └── LICENSE ├── go-openapi │ ├── jsonpointer │ │ └── LICENSE │ └── swag │ │ └── LICENSE ├── go-viper │ └── mapstructure │ │ └── v2 │ │ └── LICENSE ├── google │ ├── go-github │ │ └── v69 │ │ │ └── github │ │ │ └── LICENSE │ ├── go-querystring │ │ └── query │ │ │ └── LICENSE │ └── uuid │ │ └── LICENSE ├── inconshreveable │ └── mousetrap │ │ └── LICENSE ├── josephburnett │ └── jd │ │ └── v2 │ │ └── LICENSE ├── josharian │ └── intern │ │ └── license.md ├── mailru │ └── easyjson │ │ └── LICENSE ├── mark3labs │ └── mcp-go │ │ └── LICENSE ├── pelletier │ └── go-toml │ │ └── v2 │ │ └── LICENSE ├── sagikazarmark │ └── locafero │ │ └── LICENSE ├── shurcooL │ ├── githubv4 │ │ └── LICENSE │ └── graphql │ │ └── LICENSE ├── sirupsen │ └── logrus │ │ └── LICENSE ├── sourcegraph │ └── conc │ │ └── LICENSE ├── spf13 │ ├── afero │ │ └── LICENSE.txt │ ├── cast │ │ └── LICENSE │ ├── cobra │ │ └── LICENSE.txt │ ├── pflag │ │ └── LICENSE │ └── viper │ │ └── LICENSE ├── subosito │ └── gotenv │ │ └── LICENSE ├── yosida95 │ └── uritemplate │ │ └── v3 │ │ └── LICENSE └── yudai │ └── golcs │ └── LICENSE ├── golang.org └── x │ ├── exp │ └── LICENSE │ ├── sys │ ├── unix │ │ └── LICENSE │ └── windows │ │ └── LICENSE │ └── text │ └── LICENSE └── gopkg.in ├── yaml.v2 ├── LICENSE └── NOTICE └── yaml.v3 ├── LICENSE └── NOTICE /.dockerignore: -------------------------------------------------------------------------------- 1 | .github 2 | .vscode 3 | script 4 | third-party 5 | .dockerignore 6 | .gitignore 7 | **/*.yml 8 | **/*.yaml 9 | **/*.md 10 | **/*_test.go 11 | LICENSE 12 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @github/github-mcp-server 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug report" 3 | about: Report a bug or unexpected behavior while using GitHub MCP Server 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Describe the bug 11 | 12 | A clear and concise description of what the bug is. 13 | 14 | ### Affected version 15 | 16 | Please run ` docker run -i --rm ghcr.io/github/github-mcp-server ./github-mcp-server --version` and paste the output below 17 | 18 | ### Steps to reproduce the behavior 19 | 20 | 1. Type this '...' 21 | 2. View the output '....' 22 | 3. See error 23 | 24 | ### Expected vs actual behavior 25 | 26 | A clear and concise description of what you expected to happen and what actually happened. 27 | 28 | ### Logs 29 | 30 | Paste any available logs. Redact if needed. 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "⭐ Submit a feature request" 3 | about: Surface a feature or problem that you think should be solved 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Describe the feature or problem you’d like to solve 11 | 12 | A clear and concise description of what the feature or problem is. 13 | 14 | ### Proposed solution 15 | 16 | How will it benefit GitHub MCP Server and its users? 17 | 18 | ### Additional context 19 | 20 | Add any other context like screenshots or mockups are helpful, if applicable. -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gomod" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | - package-ecosystem: "docker" 13 | directory: "/" 14 | schedule: 15 | interval: "weekly" 16 | -------------------------------------------------------------------------------- /.github/licenses.tmpl: -------------------------------------------------------------------------------- 1 | # GitHub MCP Server dependencies 2 | 3 | The following open source dependencies are used to build the [github/github-mcp-server][] GitHub Model Context Protocol Server. 4 | 5 | ## Go Packages 6 | 7 | Some packages may only be included on certain architectures or operating systems. 8 | 9 | {{ range . }} 10 | - [{{.Name}}](https://pkg.go.dev/{{.Name}}) ([{{.LicenseName}}]({{.LicenseURL}})) 11 | {{- end }} 12 | 13 | [github/github-mcp-server]: https://github.com/github/github-mcp-server 14 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | Closes: 12 | -------------------------------------------------------------------------------- /.github/workflows/code-scanning.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | run-name: ${{ github.event.inputs.code_scanning_run_name }} 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | concurrency: 6 | group: ${{ github.workflow }}-${{ github.ref }} 7 | cancel-in-progress: true 8 | 9 | env: 10 | CODE_SCANNING_REF: ${{ github.event.inputs.code_scanning_ref }} 11 | CODE_SCANNING_BASE_BRANCH: ${{ github.event.inputs.code_scanning_base_branch }} 12 | CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH: ${{ github.event.inputs.code_scanning_is_analyzing_default_branch }} 13 | 14 | jobs: 15 | analyze: 16 | name: Analyze (${{ matrix.language }}) 17 | runs-on: ${{ fromJSON(matrix.runner) }} 18 | permissions: 19 | actions: read 20 | contents: read 21 | packages: read 22 | security-events: write 23 | continue-on-error: false 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | include: 28 | - language: actions 29 | category: /language:actions 30 | build-mode: none 31 | runner: '["ubuntu-22.04"]' 32 | - language: go 33 | category: /language:go 34 | build-mode: autobuild 35 | runner: '["ubuntu-22.04"]' 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v4 39 | 40 | - name: Initialize CodeQL 41 | uses: github/codeql-action/init@v3 42 | with: 43 | languages: ${{ matrix.language }} 44 | build-mode: ${{ matrix.build-mode }} 45 | dependency-caching: ${{ runner.environment == 'github-hosted' }} 46 | queries: "" # Default query suite 47 | packs: github/ccr-${{ matrix.language }}-queries 48 | config: | 49 | default-setup: 50 | org: 51 | model-packs: [ ${{ github.event.inputs.code_scanning_codeql_packs }} ] 52 | threat-models: [ ] 53 | - name: Setup proxy for registries 54 | id: proxy 55 | uses: github/codeql-action/start-proxy@v3 56 | with: 57 | registries_credentials: ${{ secrets.GITHUB_REGISTRIES_PROXY }} 58 | language: ${{ matrix.language }} 59 | 60 | - name: Configure 61 | uses: github/codeql-action/resolve-environment@v3 62 | id: resolve-environment 63 | with: 64 | language: ${{ matrix.language }} 65 | - name: Setup Go 66 | uses: actions/setup-go@v5 67 | if: matrix.language == 'go' && fromJSON(steps.resolve-environment.outputs.environment).configuration.go.version 68 | with: 69 | go-version: ${{ fromJSON(steps.resolve-environment.outputs.environment).configuration.go.version }} 70 | cache: false 71 | 72 | - name: Autobuild 73 | uses: github/codeql-action/autobuild@v3 74 | 75 | - name: Perform CodeQL Analysis 76 | uses: github/codeql-action/analyze@v3 77 | env: 78 | CODEQL_PROXY_HOST: ${{ steps.proxy.outputs.proxy_host }} 79 | CODEQL_PROXY_PORT: ${{ steps.proxy.outputs.proxy_port }} 80 | CODEQL_PROXY_CA_CERTIFICATE: ${{ steps.proxy.outputs.proxy_ca_certificate }} 81 | with: 82 | category: ${{ matrix.category }} 83 | -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | # This workflow uses actions that are not certified by GitHub. 4 | # They are provided by a third-party and are governed by 5 | # separate terms of service, privacy policy, and support 6 | # documentation. 7 | 8 | on: 9 | schedule: 10 | - cron: "27 0 * * *" 11 | push: 12 | branches: ["main"] 13 | # Publish semver tags as releases. 14 | tags: ["v*.*.*"] 15 | pull_request: 16 | branches: ["main"] 17 | 18 | env: 19 | # Use docker.io for Docker Hub if empty 20 | REGISTRY: ghcr.io 21 | # github.repository as / 22 | IMAGE_NAME: ${{ github.repository }} 23 | 24 | jobs: 25 | build: 26 | runs-on: ubuntu-latest-xl 27 | permissions: 28 | contents: read 29 | packages: write 30 | # This is used to complete the identity challenge 31 | # with sigstore/fulcio when running outside of PRs. 32 | id-token: write 33 | 34 | steps: 35 | - name: Checkout repository 36 | uses: actions/checkout@v4 37 | 38 | # Install the cosign tool except on PR 39 | # https://github.com/sigstore/cosign-installer 40 | - name: Install cosign 41 | if: github.event_name != 'pull_request' 42 | uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 #v3.5.0 43 | with: 44 | cosign-release: "v2.2.4" 45 | 46 | # Set up BuildKit Docker container builder to be able to build 47 | # multi-platform images and export cache 48 | # https://github.com/docker/setup-buildx-action 49 | - name: Set up Docker Buildx 50 | uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 51 | 52 | # Login against a Docker registry except on PR 53 | # https://github.com/docker/login-action 54 | - name: Log into registry ${{ env.REGISTRY }} 55 | if: github.event_name != 'pull_request' 56 | uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 57 | with: 58 | registry: ${{ env.REGISTRY }} 59 | username: ${{ github.actor }} 60 | password: ${{ secrets.GITHUB_TOKEN }} 61 | 62 | # Extract metadata (tags, labels) for Docker 63 | # https://github.com/docker/metadata-action 64 | - name: Extract Docker metadata 65 | id: meta 66 | uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 67 | with: 68 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 69 | tags: | 70 | type=schedule 71 | type=ref,event=branch 72 | type=ref,event=tag 73 | type=ref,event=pr 74 | type=semver,pattern={{version}} 75 | type=semver,pattern={{major}}.{{minor}} 76 | type=semver,pattern={{major}} 77 | type=sha 78 | type=edge 79 | # Custom rule to prevent pre-releases from getting latest tag 80 | type=raw,value=latest,enable=${{ github.ref_type == 'tag' && startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '-') }} 81 | 82 | - name: Go Build Cache for Docker 83 | uses: actions/cache@v4 84 | with: 85 | path: go-build-cache 86 | key: ${{ runner.os }}-go-build-cache-${{ hashFiles('**/go.sum') }} 87 | 88 | - name: Inject go-build-cache 89 | uses: reproducible-containers/buildkit-cache-dance@4b2444fec0c0fb9dbf175a96c094720a692ef810 # v2.1.4 90 | with: 91 | cache-source: go-build-cache 92 | 93 | # Build and push Docker image with Buildx (don't push on PR) 94 | # https://github.com/docker/build-push-action 95 | - name: Build and push Docker image 96 | id: build-and-push 97 | uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 98 | with: 99 | context: . 100 | push: ${{ github.event_name != 'pull_request' }} 101 | tags: ${{ steps.meta.outputs.tags }} 102 | labels: ${{ steps.meta.outputs.labels }} 103 | cache-from: type=gha 104 | cache-to: type=gha,mode=max 105 | platforms: linux/amd64,linux/arm64 106 | build-args: | 107 | VERSION=${{ github.ref_name }} 108 | 109 | # Sign the resulting Docker image digest except on PRs. 110 | # This will only write to the public Rekor transparency log when the Docker 111 | # repository is public to avoid leaking data. If you would like to publish 112 | # transparency data even for private images, pass --force to cosign below. 113 | # https://github.com/sigstore/cosign 114 | - name: Sign the published Docker image 115 | if: ${{ github.event_name != 'pull_request' }} 116 | env: 117 | # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable 118 | TAGS: ${{ steps.meta.outputs.tags }} 119 | DIGEST: ${{ steps.build-and-push.outputs.digest }} 120 | # This step uses the identity token to provision an ephemeral certificate 121 | # against the sigstore community Fulcio instance. 122 | run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST} 123 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Unit Tests 2 | on: [push, pull_request] 3 | 4 | permissions: 5 | contents: read 6 | 7 | jobs: 8 | build: 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | os: [ubuntu-latest, windows-latest, macos-latest] 13 | 14 | runs-on: ${{ matrix.os }} 15 | 16 | steps: 17 | - name: Check out code 18 | uses: actions/checkout@v4 19 | 20 | - name: Set up Go 21 | uses: actions/setup-go@v5 22 | with: 23 | go-version-file: "go.mod" 24 | 25 | - name: Download dependencies 26 | run: go mod download 27 | 28 | - name: Run unit tests 29 | run: go test -race ./... 30 | 31 | - name: Build 32 | run: go build -v ./cmd/github-mcp-server 33 | -------------------------------------------------------------------------------- /.github/workflows/goreleaser.yml: -------------------------------------------------------------------------------- 1 | name: GoReleaser Release 2 | on: 3 | push: 4 | tags: 5 | - "v*" 6 | permissions: 7 | contents: write 8 | id-token: write 9 | attestations: write 10 | 11 | jobs: 12 | release: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Check out code 17 | uses: actions/checkout@v4 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v5 21 | with: 22 | go-version-file: "go.mod" 23 | 24 | - name: Download dependencies 25 | run: go mod download 26 | 27 | - name: Run GoReleaser 28 | uses: goreleaser/goreleaser-action@9c156ee8a17a598857849441385a2041ef570552 29 | with: 30 | distribution: goreleaser 31 | # GoReleaser version 32 | version: "~> v2" 33 | # Arguments to pass to GoReleaser 34 | args: release --clean 35 | workdir: . 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | 39 | - name: Generate signed build provenance attestations for workflow artifacts 40 | uses: actions/attest-build-provenance@v2 41 | with: 42 | subject-path: | 43 | dist/*.tar.gz 44 | dist/*.zip 45 | dist/*.txt 46 | -------------------------------------------------------------------------------- /.github/workflows/license-check.yml: -------------------------------------------------------------------------------- 1 | # Create a github action that runs the license check script and fails if it exits with a non-zero status 2 | 3 | name: License Check 4 | on: [push, pull_request] 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | license-check: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Check out code 14 | uses: actions/checkout@v4 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v5 18 | with: 19 | go-version-file: "go.mod" 20 | - name: check licenses 21 | run: ./script/licenses-check 22 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: 3 | push: 4 | pull_request: 5 | 6 | permissions: 7 | contents: read 8 | 9 | jobs: 10 | lint: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Check out code 15 | uses: actions/checkout@v4 16 | 17 | - name: Set up Go 18 | uses: actions/setup-go@v5 19 | with: 20 | go-version-file: 'go.mod' 21 | 22 | - name: Verify dependencies 23 | run: | 24 | go mod verify 25 | go mod download 26 | 27 | - name: Run checks 28 | run: | 29 | STATUS=0 30 | assert-nothing-changed() { 31 | local diff 32 | "$@" >/dev/null || return 1 33 | if ! diff="$(git diff -U1 --color --exit-code)"; then 34 | printf '\e[31mError: running `\e[1m%s\e[22m` results in modifications that you must check into version control:\e[0m\n%s\n\n' "$*" "$diff" >&2 35 | git checkout -- . 36 | STATUS=1 37 | fi 38 | } 39 | assert-nothing-changed go mod tidy 40 | exit $STATUS 41 | 42 | - name: golangci-lint 43 | uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 44 | with: 45 | version: v2.1.6 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | cmd/github-mcp-server/github-mcp-server 3 | 4 | # VSCode 5 | .vscode/* 6 | !.vscode/launch.json 7 | 8 | # Added by goreleaser init: 9 | dist/ 10 | __debug_bin* 11 | 12 | # Go 13 | vendor 14 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # https://golangci-lint.run/usage/configuration 2 | version: "2" 3 | 4 | run: 5 | timeout: 5m 6 | tests: true 7 | concurrency: 4 8 | 9 | linters: 10 | enable: 11 | - govet 12 | - errcheck 13 | - staticcheck 14 | - revive 15 | - ineffassign 16 | - unused 17 | - misspell 18 | - nakedret 19 | - bodyclose 20 | - gocritic 21 | - makezero 22 | - gosec 23 | settings: 24 | staticcheck: 25 | checks: 26 | - all 27 | - '-QF1008' # Allow embedded structs to be referenced by field 28 | - '-ST1000' # Do not require package comments 29 | revive: 30 | rules: 31 | - name: exported 32 | disabled: true 33 | - name: exported 34 | disabled: true 35 | - name: package-comments 36 | disabled: true 37 | 38 | formatters: 39 | enable: 40 | - gofmt 41 | - goimports 42 | 43 | output: 44 | formats: 45 | text: 46 | print-linter-name: true 47 | print-issued-lines: true 48 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | project_name: github-mcp-server 3 | before: 4 | hooks: 5 | - go mod tidy 6 | - go generate ./... 7 | 8 | builds: 9 | - env: 10 | - CGO_ENABLED=0 11 | ldflags: 12 | - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} 13 | goos: 14 | - linux 15 | - windows 16 | - darwin 17 | main: ./cmd/github-mcp-server 18 | 19 | archives: 20 | - formats: tar.gz 21 | # this name template makes the OS and Arch compatible with the results of `uname`. 22 | name_template: >- 23 | {{ .ProjectName }}_ 24 | {{- title .Os }}_ 25 | {{- if eq .Arch "amd64" }}x86_64 26 | {{- else if eq .Arch "386" }}i386 27 | {{- else }}{{ .Arch }}{{ end }} 28 | {{- if .Arm }}v{{ .Arm }}{{ end }} 29 | # use zip for windows archives 30 | format_overrides: 31 | - goos: windows 32 | formats: zip 33 | 34 | changelog: 35 | sort: asc 36 | filters: 37 | exclude: 38 | - "^docs:" 39 | - "^test:" 40 | 41 | release: 42 | draft: true 43 | prerelease: auto 44 | name_template: "GitHub MCP Server {{.Version}}" 45 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch stdio server", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "auto", 12 | "cwd": "${workspaceFolder}", 13 | "program": "cmd/github-mcp-server/main.go", 14 | "args": ["stdio"], 15 | "console": "integratedTerminal", 16 | }, 17 | { 18 | "name": "Launch stdio server (read-only)", 19 | "type": "go", 20 | "request": "launch", 21 | "mode": "auto", 22 | "cwd": "${workspaceFolder}", 23 | "program": "cmd/github-mcp-server/main.go", 24 | "args": ["stdio", "--read-only"], 25 | "console": "integratedTerminal", 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | GitHub. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | [fork]: https://github.com/github/github-mcp-server/fork 4 | [pr]: https://github.com/github/github-mcp-server/compare 5 | [style]: https://github.com/github/github-mcp-server/blob/main/.golangci.yml 6 | 7 | Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. 8 | 9 | Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE). 10 | 11 | Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. 12 | 13 | ## Prerequisites for running and testing code 14 | 15 | These are one time installations required to be able to test your changes locally as part of the pull request (PR) submission process. 16 | 17 | 1. install Go [through download](https://go.dev/doc/install) | [through Homebrew](https://formulae.brew.sh/formula/go) 18 | 1. [install golangci-lint v2](https://golangci-lint.run/welcome/install/#local-installation) 19 | 20 | ## Submitting a pull request 21 | 22 | 1. [Fork][fork] and clone the repository 23 | 1. Make sure the tests pass on your machine: `go test -v ./...` 24 | 1. Make sure linter passes on your machine: `golangci-lint run` 25 | 1. Create a new branch: `git checkout -b my-branch-name` 26 | 1. Make your change, add tests, and make sure the tests and linter still pass 27 | 1. Push to your fork and [submit a pull request][pr] 28 | 1. Pat yourself on the back and wait for your pull request to be reviewed and merged. 29 | 30 | Here are a few things you can do that will increase the likelihood of your pull request being accepted: 31 | 32 | - Follow the [style guide][style]. 33 | - Write tests. 34 | - Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. 35 | - Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). 36 | 37 | ## Resources 38 | 39 | - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) 40 | - [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) 41 | - [GitHub Help](https://help.github.com) 42 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.24.3-alpine AS build 2 | ARG VERSION="dev" 3 | 4 | # Set the working directory 5 | WORKDIR /build 6 | 7 | # Install git 8 | RUN --mount=type=cache,target=/var/cache/apk \ 9 | apk add git 10 | 11 | # Build the server 12 | # go build automatically download required module dependencies to /go/pkg/mod 13 | RUN --mount=type=cache,target=/go/pkg/mod \ 14 | --mount=type=cache,target=/root/.cache/go-build \ 15 | --mount=type=bind,target=. \ 16 | CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=${VERSION} -X main.commit=$(git rev-parse HEAD) -X main.date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ 17 | -o /bin/github-mcp-server cmd/github-mcp-server/main.go 18 | 19 | # Make a stage to run the app 20 | FROM gcr.io/distroless/base-debian12 21 | # Set the working directory 22 | WORKDIR /server 23 | # Copy the binary from the build stage 24 | COPY --from=build /bin/github-mcp-server . 25 | # Command to run the server 26 | CMD ["./github-mcp-server", "stdio"] 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 GitHub 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | Thanks for helping make GitHub safe for everyone. 2 | 3 | # Security 4 | 5 | GitHub takes the security of our software products and services seriously, including all of the open source code repositories managed through our GitHub organizations, such as [GitHub](https://github.com/GitHub). 6 | 7 | Even though [open source repositories are outside of the scope of our bug bounty program](https://bounty.github.com/index.html#scope) and therefore not eligible for bounty rewards, we will ensure that your finding gets passed along to the appropriate maintainers for remediation. 8 | 9 | ## Reporting Security Issues 10 | 11 | If you believe you have found a security vulnerability in any GitHub-owned repository, please report it to us through coordinated disclosure. 12 | 13 | **Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** 14 | 15 | Instead, please send an email to opensource-security[@]github.com. 16 | 17 | Please include as much of the information listed below as you can to help us better understand and resolve the issue: 18 | 19 | * The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting) 20 | * Full paths of source file(s) related to the manifestation of the issue 21 | * The location of the affected source code (tag/branch/commit or direct URL) 22 | * Any special configuration required to reproduce the issue 23 | * Step-by-step instructions to reproduce the issue 24 | * Proof-of-concept or exploit code (if possible) 25 | * Impact of the issue, including how an attacker might exploit the issue 26 | 27 | This information will help us triage your report more quickly. 28 | 29 | ## Policy 30 | 31 | See [GitHub's Safe Harbor Policy](https://docs.github.com/en/site-policy/security-policies/github-bug-bounty-program-legal-safe-harbor#1-safe-harbor-terms) 32 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | ## How to file issues and get help 4 | 5 | This project uses GitHub issues to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a new issue. 6 | 7 | For help or questions about using this project, please open an issue. 8 | 9 | - The `github-mcp-server` is under active development and maintained by GitHub staff **AND THE COMMUNITY**. We will do our best to respond to support, feature requests, and community questions in a timely manner. 10 | 11 | ## GitHub Support Policy 12 | 13 | Support for this project is limited to the resources listed above. 14 | -------------------------------------------------------------------------------- /cmd/github-mcp-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/github/github-mcp-server/internal/ghmcp" 9 | "github.com/github/github-mcp-server/pkg/github" 10 | "github.com/spf13/cobra" 11 | "github.com/spf13/viper" 12 | ) 13 | 14 | // These variables are set by the build process using ldflags. 15 | var version = "version" 16 | var commit = "commit" 17 | var date = "date" 18 | 19 | var ( 20 | rootCmd = &cobra.Command{ 21 | Use: "server", 22 | Short: "GitHub MCP Server", 23 | Long: `A GitHub MCP server that handles various tools and resources.`, 24 | Version: fmt.Sprintf("Version: %s\nCommit: %s\nBuild Date: %s", version, commit, date), 25 | } 26 | 27 | stdioCmd = &cobra.Command{ 28 | Use: "stdio", 29 | Short: "Start stdio server", 30 | Long: `Start a server that communicates via standard input/output streams using JSON-RPC messages.`, 31 | RunE: func(_ *cobra.Command, _ []string) error { 32 | token := viper.GetString("personal_access_token") 33 | if token == "" { 34 | return errors.New("GITHUB_PERSONAL_ACCESS_TOKEN not set") 35 | } 36 | 37 | // If you're wondering why we're not using viper.GetStringSlice("toolsets"), 38 | // it's because viper doesn't handle comma-separated values correctly for env 39 | // vars when using GetStringSlice. 40 | // https://github.com/spf13/viper/issues/380 41 | var enabledToolsets []string 42 | if err := viper.UnmarshalKey("toolsets", &enabledToolsets); err != nil { 43 | return fmt.Errorf("failed to unmarshal toolsets: %w", err) 44 | } 45 | 46 | stdioServerConfig := ghmcp.StdioServerConfig{ 47 | Version: version, 48 | Host: viper.GetString("host"), 49 | Token: token, 50 | EnabledToolsets: enabledToolsets, 51 | DynamicToolsets: viper.GetBool("dynamic_toolsets"), 52 | ReadOnly: viper.GetBool("read-only"), 53 | ExportTranslations: viper.GetBool("export-translations"), 54 | EnableCommandLogging: viper.GetBool("enable-command-logging"), 55 | LogFilePath: viper.GetString("log-file"), 56 | } 57 | 58 | return ghmcp.RunStdioServer(stdioServerConfig) 59 | }, 60 | } 61 | ) 62 | 63 | func init() { 64 | cobra.OnInitialize(initConfig) 65 | 66 | rootCmd.SetVersionTemplate("{{.Short}}\n{{.Version}}\n") 67 | 68 | // Add global flags that will be shared by all commands 69 | rootCmd.PersistentFlags().StringSlice("toolsets", github.DefaultTools, "An optional comma separated list of groups of tools to allow, defaults to enabling all") 70 | rootCmd.PersistentFlags().Bool("dynamic-toolsets", false, "Enable dynamic toolsets") 71 | rootCmd.PersistentFlags().Bool("read-only", false, "Restrict the server to read-only operations") 72 | rootCmd.PersistentFlags().String("log-file", "", "Path to log file") 73 | rootCmd.PersistentFlags().Bool("enable-command-logging", false, "When enabled, the server will log all command requests and responses to the log file") 74 | rootCmd.PersistentFlags().Bool("export-translations", false, "Save translations to a JSON file") 75 | rootCmd.PersistentFlags().String("gh-host", "", "Specify the GitHub hostname (for GitHub Enterprise etc.)") 76 | 77 | // Bind flag to viper 78 | _ = viper.BindPFlag("toolsets", rootCmd.PersistentFlags().Lookup("toolsets")) 79 | _ = viper.BindPFlag("dynamic_toolsets", rootCmd.PersistentFlags().Lookup("dynamic-toolsets")) 80 | _ = viper.BindPFlag("read-only", rootCmd.PersistentFlags().Lookup("read-only")) 81 | _ = viper.BindPFlag("log-file", rootCmd.PersistentFlags().Lookup("log-file")) 82 | _ = viper.BindPFlag("enable-command-logging", rootCmd.PersistentFlags().Lookup("enable-command-logging")) 83 | _ = viper.BindPFlag("export-translations", rootCmd.PersistentFlags().Lookup("export-translations")) 84 | _ = viper.BindPFlag("host", rootCmd.PersistentFlags().Lookup("gh-host")) 85 | 86 | // Add subcommands 87 | rootCmd.AddCommand(stdioCmd) 88 | } 89 | 90 | func initConfig() { 91 | // Initialize Viper configuration 92 | viper.SetEnvPrefix("github") 93 | viper.AutomaticEnv() 94 | } 95 | 96 | func main() { 97 | if err := rootCmd.Execute(); err != nil { 98 | fmt.Fprintf(os.Stderr, "%v\n", err) 99 | os.Exit(1) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /cmd/mcpcurl/README.md: -------------------------------------------------------------------------------- 1 | # mcpcurl 2 | 3 | A CLI tool that dynamically builds commands based on schemas retrieved from MCP servers that can 4 | be executed against the configured MCP server. 5 | 6 | ## Overview 7 | 8 | `mcpcurl` is a command-line interface that: 9 | 10 | 1. Connects to an MCP server via stdio 11 | 2. Dynamically retrieves the available tools schema 12 | 3. Generates CLI commands corresponding to each tool 13 | 4. Handles parameter validation based on the schema 14 | 5. Executes commands and displays responses 15 | 16 | ## Installation 17 | 18 | ## Usage 19 | 20 | ```console 21 | mcpcurl --stdio-server-cmd="" [flags] 22 | ``` 23 | 24 | The `--stdio-server-cmd` flag is required for all commands and specifies the command to run the MCP server. 25 | 26 | ### Available Commands 27 | 28 | - `tools`: Contains all dynamically generated tool commands from the schema 29 | - `schema`: Fetches and displays the raw schema from the MCP server 30 | - `help`: Shows help for any command 31 | 32 | ### Examples 33 | 34 | List available tools in Anthropic's MCP server: 35 | 36 | ```console 37 | % ./mcpcurl --stdio-server-cmd "docker run -i --rm -e GITHUB_PERSONAL_ACCESS_TOKEN mcp/github" tools --help 38 | Contains all dynamically generated tool commands from the schema 39 | 40 | Usage: 41 | mcpcurl tools [command] 42 | 43 | Available Commands: 44 | add_issue_comment Add a comment to an existing issue 45 | create_branch Create a new branch in a GitHub repository 46 | create_issue Create a new issue in a GitHub repository 47 | create_or_update_file Create or update a single file in a GitHub repository 48 | create_pull_request Create a new pull request in a GitHub repository 49 | create_repository Create a new GitHub repository in your account 50 | fork_repository Fork a GitHub repository to your account or specified organization 51 | get_file_contents Get the contents of a file or directory from a GitHub repository 52 | get_issue Get details of a specific issue in a GitHub repository 53 | get_issue_comments Get comments for a GitHub issue 54 | list_commits Get list of commits of a branch in a GitHub repository 55 | list_issues List issues in a GitHub repository with filtering options 56 | push_files Push multiple files to a GitHub repository in a single commit 57 | search_code Search for code across GitHub repositories 58 | search_issues Search for issues and pull requests across GitHub repositories 59 | search_repositories Search for GitHub repositories 60 | search_users Search for users on GitHub 61 | update_issue Update an existing issue in a GitHub repository 62 | 63 | Flags: 64 | -h, --help help for tools 65 | 66 | Global Flags: 67 | --pretty Pretty print MCP response (only for JSON responses) (default true) 68 | --stdio-server-cmd string Shell command to invoke MCP server via stdio (required) 69 | 70 | Use "mcpcurl tools [command] --help" for more information about a command. 71 | ``` 72 | 73 | Get help for a specific tool: 74 | 75 | ```console 76 | % ./mcpcurl --stdio-server-cmd "docker run -i --rm -e GITHUB_PERSONAL_ACCESS_TOKEN mcp/github" tools get_issue --help 77 | Get details of a specific issue in a GitHub repository 78 | 79 | Usage: 80 | mcpcurl tools get_issue [flags] 81 | 82 | Flags: 83 | -h, --help help for get_issue 84 | --issue_number float 85 | --owner string 86 | --repo string 87 | 88 | Global Flags: 89 | --pretty Pretty print MCP response (only for JSON responses) (default true) 90 | --stdio-server-cmd string Shell command to invoke MCP server via stdio (required) 91 | 92 | ``` 93 | 94 | Use one of the tools: 95 | 96 | ```console 97 | % ./mcpcurl --stdio-server-cmd "docker run -i --rm -e GITHUB_PERSONAL_ACCESS_TOKEN mcp/github" tools get_issue --owner golang --repo go --issue_number 1 98 | { 99 | "active_lock_reason": null, 100 | "assignee": null, 101 | "assignees": [], 102 | "author_association": "CONTRIBUTOR", 103 | "body": "by **rsc+personal@swtch.com**:\n\n\u003cpre\u003eWhat steps will reproduce the problem?\n1. Run build on Ubuntu 9.10, which uses gcc 4.4.1\n\nWhat is the expected output? What do you see instead?\n\nCgo fails with the following error:\n\n{{{\ngo/misc/cgo/stdio$ make\ncgo file.go\ncould not determine kind of name for C.CString\ncould not determine kind of name for C.puts\ncould not determine kind of name for C.fflushstdout\ncould not determine kind of name for C.free\nthrow: sys·mapaccess1: key not in map\n\npanic PC=0x2b01c2b96a08\nthrow+0x33 /media/scratch/workspace/go/src/pkg/runtime/runtime.c:71\n throw(0x4d2daf, 0x0)\nsys·mapaccess1+0x74 \n/media/scratch/workspace/go/src/pkg/runtime/hashmap.c:769\n sys·mapaccess1(0xc2b51930, 0x2b01)\nmain·*Prog·loadDebugInfo+0xa67 \n/media/scratch/workspace/go/src/cmd/cgo/gcc.go:164\n main·*Prog·loadDebugInfo(0xc2bc0000, 0x2b01)\nmain·main+0x352 \n/media/scratch/workspace/go/src/cmd/cgo/main.go:68\n main·main()\nmainstart+0xf \n/media/scratch/workspace/go/src/pkg/runtime/amd64/asm.s:55\n mainstart()\ngoexit /media/scratch/workspace/go/src/pkg/runtime/proc.c:133\n goexit()\nmake: *** [file.cgo1.go] Error 2\n}}}\n\nPlease use labels and text to provide additional information.\u003c/pre\u003e\n", 104 | "closed_at": "2014-12-08T10:02:16Z", 105 | "closed_by": null, 106 | "comments": 12, 107 | "comments_url": "https://api.github.com/repos/golang/go/issues/1/comments", 108 | "created_at": "2009-10-22T06:07:26Z", 109 | "events_url": "https://api.github.com/repos/golang/go/issues/1/events", 110 | [...] 111 | } 112 | ``` 113 | 114 | ## Dynamic Commands 115 | 116 | All tools provided by the MCP server are automatically available as subcommands under the `tools` command. Each generated command has: 117 | 118 | - Appropriate flags matching the tool's input schema 119 | - Validation for required parameters 120 | - Type validation 121 | - Enum validation (for string parameters with allowable values) 122 | - Help text generated from the tool's description 123 | 124 | ## How It Works 125 | 126 | 1. `mcpcurl` makes a JSON-RPC request to the server using the `tools/list` method 127 | 2. The server responds with a schema describing all available tools 128 | 3. `mcpcurl` dynamically builds a command structure based on this schema 129 | 4. When a command is executed, arguments are converted to a JSON-RPC request 130 | 5. The request is sent to the server via stdin, and the response is printed to stdout 131 | -------------------------------------------------------------------------------- /docs/testing.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | This project uses a combination of unit tests and end-to-end (e2e) tests to ensure correctness and stability. 4 | 5 | ## Unit Testing Patterns 6 | 7 | - Unit tests are located alongside implementation, with filenames ending in `_test.go`. 8 | - Currently the preference is to use internal tests i.e. test files do not have `_test` package suffix. 9 | - Tests use [testify](https://github.com/stretchr/testify) for assertions and require statements. Use `require` when continuing the test is not meaningful, for example it is almost never correct to continue after an error expectation. 10 | - Mocking is performed using [go-github-mock](https://github.com/migueleliasweb/go-github-mock) or `githubv4mock` for simulating GitHub rest and GQL API responses. 11 | - Each tool's schema is snapshotted and checked for changes using the `toolsnaps` utility (see below). 12 | - Tests are designed to be explicit and verbose to aid maintainability and clarity. 13 | - Handler unit tests should take the form of: 14 | 1. Test tool snapshot 15 | 1. Very important expectations against the schema (e.g. `ReadOnly` annotation) 16 | 1. Behavioural tests in table-driven form 17 | 18 | ## End-to-End (e2e) Tests 19 | 20 | - E2E tests are located in the [`e2e/`](../e2e/) directory. See the [e2e/README.md](../e2e/README.md) for full details on running and debugging these tests. 21 | 22 | ## toolsnaps: Tool Schema Snapshots 23 | 24 | - The `toolsnaps` utility ensures that the JSON schema for each tool does not change unexpectedly. 25 | - Snapshots are stored in `__toolsnaps__/*.snap` files , where `*` represents the name of the tool 26 | - When running tests, the current tool schema is compared to the snapshot. If there is a difference, the test will fail and show a diff. 27 | - If you intentionally change a tool's schema, update the snapshots by running tests with the environment variable: `UPDATE_TOOLSNAPS=true go test ./...` 28 | - In CI (when `GITHUB_ACTIONS=true`), missing snapshots will cause a test failure to ensure snapshots are always 29 | committed. 30 | 31 | ## Notes 32 | 33 | - Some tools that mutate global state (e.g., marking all notifications as read) are tested primarily with unit tests, not e2e, to avoid side effects. 34 | - For more on the limitations and philosophy of the e2e suite, see the [e2e/README.md](../e2e/README.md). 35 | -------------------------------------------------------------------------------- /e2e/README.md: -------------------------------------------------------------------------------- 1 | # End To End (e2e) Tests 2 | 3 | The purpose of the E2E tests is to have a simple (currently) test that gives maintainers some confidence in the black box behavior of our artifacts. It does this by: 4 | * Building the `github-mcp-server` docker image 5 | * Running the image 6 | * Interacting with the server via stdio 7 | * Issuing requests that interact with the live GitHub API 8 | 9 | ## Running the Tests 10 | 11 | A service must be running that supports image building and container creation via the `docker` CLI. 12 | 13 | Since these tests require a token to interact with real resources on the GitHub API, it is gated behind the `e2e` build flag. 14 | 15 | ``` 16 | GITHUB_MCP_SERVER_E2E_TOKEN= go test -v --tags e2e ./e2e 17 | ``` 18 | 19 | The `GITHUB_MCP_SERVER_E2E_TOKEN` environment variable is mapped to `GITHUB_PERSONAL_ACCESS_TOKEN` internally, but separated to avoid accidental reuse of credentials. 20 | 21 | ## Example 22 | 23 | The following diff adjusts the `get_me` tool to return `foobar` as the user login. 24 | 25 | ```diff 26 | diff --git a/pkg/github/context_tools.go b/pkg/github/context_tools.go 27 | index 1c91d70..ac4ef2b 100644 28 | --- a/pkg/github/context_tools.go 29 | +++ b/pkg/github/context_tools.go 30 | @@ -39,6 +39,8 @@ func GetMe(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mc 31 | return mcp.NewToolResultError(fmt.Sprintf("failed to get user: %s", string(body))), nil 32 | } 33 | 34 | + user.Login = sPtr("foobar") 35 | + 36 | r, err := json.Marshal(user) 37 | if err != nil { 38 | return nil, fmt.Errorf("failed to marshal user: %w", err) 39 | @@ -47,3 +49,7 @@ func GetMe(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mc 40 | return mcp.NewToolResultText(string(r)), nil 41 | } 42 | } 43 | + 44 | +func sPtr(s string) *string { 45 | + return &s 46 | +} 47 | ``` 48 | 49 | Running the tests: 50 | 51 | ``` 52 | ➜ GITHUB_MCP_SERVER_E2E_TOKEN=$(gh auth token) go test -v --tags e2e ./e2e 53 | === RUN TestE2E 54 | e2e_test.go:92: Building Docker image for e2e tests... 55 | e2e_test.go:36: Starting Stdio MCP client... 56 | === RUN TestE2E/Initialize 57 | === RUN TestE2E/CallTool_get_me 58 | e2e_test.go:85: 59 | Error Trace: /Users/williammartin/workspace/github-mcp-server/e2e/e2e_test.go:85 60 | Error: Not equal: 61 | expected: "foobar" 62 | actual : "williammartin" 63 | 64 | Diff: 65 | --- Expected 66 | +++ Actual 67 | @@ -1 +1 @@ 68 | -foobar 69 | +williammartin 70 | Test: TestE2E/CallTool_get_me 71 | Messages: expected login to match 72 | --- FAIL: TestE2E (1.05s) 73 | --- PASS: TestE2E/Initialize (0.09s) 74 | --- FAIL: TestE2E/CallTool_get_me (0.46s) 75 | FAIL 76 | FAIL github.com/github/github-mcp-server/e2e 1.433s 77 | FAIL 78 | ``` 79 | 80 | ## Debugging the Tests 81 | 82 | It is possible to provide `GITHUB_MCP_SERVER_E2E_DEBUG=true` to run the e2e tests with an in-process version of the MCP server. This has slightly reduced coverage as it doesn't integrate with Docker, or make use of the cobra/viper configuration parsing. However, it allows for placing breakpoints in the MCP Server internals, supporting much better debugging flows than the fully black-box tests. 83 | 84 | One might argue that the lack of visibility into failures for the black box tests also indicates a product need, but this solves for the immediate pain point felt as a maintainer. 85 | 86 | ## Limitations 87 | 88 | The current test suite is intentionally very limited in scope. This is because the maintenance costs on e2e tests tend to increase significantly over time. To read about some challenges with GitHub integration tests, see [go-github integration tests README](https://github.com/google/go-github/blob/5b75aa86dba5cf4af2923afa0938774f37fa0a67/test/README.md). We will expand this suite circumspectly! 89 | 90 | The tests are quite repetitive and verbose. This is intentional as we want to see them develop more before committing to abstractions. 91 | 92 | Currently, visibility into failures is not particularly good. We're hoping that we can pull apart the mcp-go client and have it hook into streams representing stdio without requiring an exec. This way we can get breakpoints in the debugger easily. 93 | 94 | ### Global State Mutation Tests 95 | 96 | Some tools (such as those that mark all notifications as read) would change the global state for the tester, and are also not idempotent, so they offer little value for end to end tests and instead should rely on unit testing and manual verifications. 97 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/github/github-mcp-server 2 | 3 | go 1.23.7 4 | 5 | require ( 6 | github.com/google/go-github/v69 v69.2.0 7 | github.com/josephburnett/jd v1.9.2 8 | github.com/mark3labs/mcp-go v0.30.0 9 | github.com/migueleliasweb/go-github-mock v1.3.0 10 | github.com/sirupsen/logrus v1.9.3 11 | github.com/spf13/cobra v1.9.1 12 | github.com/spf13/viper v1.20.1 13 | github.com/stretchr/testify v1.10.0 14 | ) 15 | 16 | require ( 17 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 18 | github.com/go-openapi/swag v0.21.1 // indirect 19 | github.com/josharian/intern v1.0.0 // indirect 20 | github.com/mailru/easyjson v0.7.7 // indirect 21 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect 22 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect 23 | gopkg.in/yaml.v2 v2.4.0 // indirect 24 | ) 25 | 26 | require ( 27 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 28 | github.com/fsnotify/fsnotify v1.8.0 // indirect 29 | github.com/go-viper/mapstructure/v2 v2.2.1 30 | github.com/google/go-github/v71 v71.0.0 // indirect 31 | github.com/google/go-querystring v1.1.0 // indirect 32 | github.com/google/uuid v1.6.0 // indirect 33 | github.com/gorilla/mux v1.8.0 // indirect 34 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 35 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect 36 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 37 | github.com/rogpeppe/go-internal v1.13.1 // indirect 38 | github.com/sagikazarmark/locafero v0.9.0 // indirect 39 | github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7 40 | github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 41 | github.com/sourcegraph/conc v0.3.0 // indirect 42 | github.com/spf13/afero v1.14.0 // indirect 43 | github.com/spf13/cast v1.7.1 // indirect 44 | github.com/spf13/pflag v1.0.6 // indirect 45 | github.com/subosito/gotenv v1.6.0 // indirect 46 | github.com/yosida95/uritemplate/v3 v3.0.2 // indirect 47 | go.uber.org/multierr v1.11.0 // indirect 48 | golang.org/x/oauth2 v0.29.0 // indirect 49 | golang.org/x/sys v0.31.0 // indirect 50 | golang.org/x/text v0.23.0 // indirect 51 | golang.org/x/time v0.5.0 // indirect 52 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 53 | gopkg.in/yaml.v3 v3.0.1 // indirect 54 | ) 55 | -------------------------------------------------------------------------------- /internal/githubv4mock/githubv4mock.go: -------------------------------------------------------------------------------- 1 | // githubv4mock package provides a mock GraphQL server used for testing queries produced via 2 | // shurcooL/githubv4 or shurcooL/graphql modules. 3 | package githubv4mock 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "net/http" 10 | ) 11 | 12 | type Matcher struct { 13 | Request string 14 | Variables map[string]any 15 | 16 | Response GQLResponse 17 | } 18 | 19 | // NewQueryMatcher constructs a new matcher for the provided query and variables. 20 | // If the provided query is a string, it will be used-as-is, otherwise it will be 21 | // converted to a string using the constructQuery function taken from shurcooL/graphql. 22 | func NewQueryMatcher(query any, variables map[string]any, response GQLResponse) Matcher { 23 | queryString, ok := query.(string) 24 | if !ok { 25 | queryString = constructQuery(query, variables) 26 | } 27 | 28 | return Matcher{ 29 | Request: queryString, 30 | Variables: variables, 31 | Response: response, 32 | } 33 | } 34 | 35 | // NewMutationMatcher constructs a new matcher for the provided mutation and variables. 36 | // If the provided mutation is a string, it will be used-as-is, otherwise it will be 37 | // converted to a string using the constructMutation function taken from shurcooL/graphql. 38 | // 39 | // The input parameter is a special form of variable, matching the usage in shurcooL/githubv4. It will be added 40 | // to the query as a variable called `input`. Furthermore, it will be converted to a map[string]any 41 | // to be used for later equality comparison, as when the http handler is called, the request body will no longer 42 | // contain the input struct type information. 43 | func NewMutationMatcher(mutation any, input any, variables map[string]any, response GQLResponse) Matcher { 44 | mutationString, ok := mutation.(string) 45 | if !ok { 46 | // Matching shurcooL/githubv4 mutation behaviour found in https://github.com/shurcooL/githubv4/blob/48295856cce734663ddbd790ff54800f784f3193/githubv4.go#L45-L56 47 | if variables == nil { 48 | variables = map[string]any{"input": input} 49 | } else { 50 | variables["input"] = input 51 | } 52 | 53 | mutationString = constructMutation(mutation, variables) 54 | m, _ := githubv4InputStructToMap(input) 55 | variables["input"] = m 56 | } 57 | 58 | return Matcher{ 59 | Request: mutationString, 60 | Variables: variables, 61 | Response: response, 62 | } 63 | } 64 | 65 | type GQLResponse struct { 66 | Data map[string]any `json:"data"` 67 | Errors []struct { 68 | Message string `json:"message"` 69 | } `json:"errors,omitempty"` 70 | } 71 | 72 | // DataResponse is the happy path response constructor for a mocked GraphQL request. 73 | func DataResponse(data map[string]any) GQLResponse { 74 | return GQLResponse{ 75 | Data: data, 76 | } 77 | } 78 | 79 | // ErrorResponse is the unhappy path response constructor for a mocked GraphQL request.\ 80 | // Note that for the moment it is only possible to return a single error message. 81 | func ErrorResponse(errorMsg string) GQLResponse { 82 | return GQLResponse{ 83 | Errors: []struct { 84 | Message string `json:"message"` 85 | }{ 86 | { 87 | Message: errorMsg, 88 | }, 89 | }, 90 | } 91 | } 92 | 93 | // githubv4InputStructToMap converts a struct to a map[string]any, it uses JSON marshalling rather than reflection 94 | // to do so, because the json struct tags are used in the real implementation to produce the variable key names, 95 | // and we need to ensure that when variable matching occurs in the http handler, the keys correctly match. 96 | func githubv4InputStructToMap(s any) (map[string]any, error) { 97 | jsonBytes, err := json.Marshal(s) 98 | if err != nil { 99 | return nil, err 100 | } 101 | 102 | var result map[string]any 103 | err = json.Unmarshal(jsonBytes, &result) 104 | return result, err 105 | } 106 | 107 | // NewMockedHTTPClient creates a new HTTP client that registers a handler for /graphql POST requests. 108 | // For each request, an attempt will be be made to match the request body against the provided matchers. 109 | // If a match is found, the corresponding response will be returned with StatusOK. 110 | // 111 | // Note that query and variable matching can be slightly fickle. The client expects an EXACT match on the query, 112 | // which in most cases will have been constructed from a type with graphql tags. The query construction code in 113 | // shurcooL/githubv4 uses the field types to derive the query string, thus a go string is not the same as a graphql.ID, 114 | // even though `type ID string`. It is therefore expected that matching variables have the right type for example: 115 | // 116 | // githubv4mock.NewQueryMatcher( 117 | // struct { 118 | // Repository struct { 119 | // PullRequest struct { 120 | // ID githubv4.ID 121 | // } `graphql:"pullRequest(number: $prNum)"` 122 | // } `graphql:"repository(owner: $owner, name: $repo)"` 123 | // }{}, 124 | // map[string]any{ 125 | // "owner": githubv4.String("owner"), 126 | // "repo": githubv4.String("repo"), 127 | // "prNum": githubv4.Int(42), 128 | // }, 129 | // githubv4mock.DataResponse( 130 | // map[string]any{ 131 | // "repository": map[string]any{ 132 | // "pullRequest": map[string]any{ 133 | // "id": "PR_kwDODKw3uc6WYN1T", 134 | // }, 135 | // }, 136 | // }, 137 | // ), 138 | // ) 139 | // 140 | // To aid in variable equality checks, values are considered equal if they approximate to the same type. This is 141 | // required because when the http handler is called, the request body no longer has the type information. This manifests 142 | // particularly when using the githubv4.Input types which have type deffed fields in their structs. For example: 143 | // 144 | // type CloseIssueInput struct { 145 | // IssueID ID `json:"issueId"` 146 | // StateReason *IssueClosedStateReason `json:"stateReason,omitempty"` 147 | // } 148 | // 149 | // This client does not currently provide a mechanism for out-of-band errors e.g. returning a 500, 150 | // and errors are constrained to GQL errors returned in the response body with a 200 status code. 151 | func NewMockedHTTPClient(ms ...Matcher) *http.Client { 152 | matchers := make(map[string]Matcher, len(ms)) 153 | for _, m := range ms { 154 | matchers[m.Request] = m 155 | } 156 | 157 | mux := http.NewServeMux() 158 | mux.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) { 159 | if r.Method != http.MethodPost { 160 | http.Error(w, "method not allowed", http.StatusMethodNotAllowed) 161 | return 162 | } 163 | 164 | gqlRequest, err := parseBody(r.Body) 165 | if err != nil { 166 | http.Error(w, "invalid request body", http.StatusBadRequest) 167 | return 168 | } 169 | defer func() { _ = r.Body.Close() }() 170 | 171 | matcher, ok := matchers[gqlRequest.Query] 172 | if !ok { 173 | http.Error(w, fmt.Sprintf("no matcher found for query %s", gqlRequest.Query), http.StatusNotFound) 174 | return 175 | } 176 | 177 | if len(gqlRequest.Variables) > 0 { 178 | if len(gqlRequest.Variables) != len(matcher.Variables) { 179 | http.Error(w, "variables do not have the same length", http.StatusBadRequest) 180 | return 181 | } 182 | 183 | for k, v := range matcher.Variables { 184 | if !objectsAreEqualValues(v, gqlRequest.Variables[k]) { 185 | http.Error(w, "variable does not match", http.StatusBadRequest) 186 | return 187 | } 188 | } 189 | } 190 | 191 | responseBody, err := json.Marshal(matcher.Response) 192 | if err != nil { 193 | http.Error(w, "error marshalling response", http.StatusInternalServerError) 194 | return 195 | } 196 | 197 | w.Header().Set("Content-Type", "application/json") 198 | w.WriteHeader(http.StatusOK) 199 | _, _ = w.Write(responseBody) 200 | }) 201 | 202 | return &http.Client{Transport: &localRoundTripper{ 203 | handler: mux, 204 | }} 205 | } 206 | 207 | type gqlRequest struct { 208 | Query string `json:"query"` 209 | Variables map[string]any `json:"variables,omitempty"` 210 | } 211 | 212 | func parseBody(r io.Reader) (gqlRequest, error) { 213 | var req gqlRequest 214 | err := json.NewDecoder(r).Decode(&req) 215 | return req, err 216 | } 217 | 218 | func Ptr[T any](v T) *T { return &v } 219 | -------------------------------------------------------------------------------- /internal/githubv4mock/local_round_tripper.go: -------------------------------------------------------------------------------- 1 | // Ths contents of this file are taken from https://github.com/shurcooL/graphql/blob/ed46e5a4646634fc16cb07c3b8db389542cc8847/graphql_test.go#L155-L165 2 | // because they are not exported by the module, and we would like to use them in building the githubv4mock test utility. 3 | // 4 | // The original license, copied from https://github.com/shurcooL/graphql/blob/ed46e5a4646634fc16cb07c3b8db389542cc8847/LICENSE 5 | // 6 | // MIT License 7 | 8 | // Copyright (c) 2017 Dmitri Shuralyov 9 | 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | package githubv4mock 28 | 29 | import ( 30 | "net/http" 31 | "net/http/httptest" 32 | ) 33 | 34 | // localRoundTripper is an http.RoundTripper that executes HTTP transactions 35 | // by using handler directly, instead of going over an HTTP connection. 36 | type localRoundTripper struct { 37 | handler http.Handler 38 | } 39 | 40 | func (l localRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 41 | w := httptest.NewRecorder() 42 | l.handler.ServeHTTP(w, req) 43 | return w.Result(), nil 44 | } 45 | -------------------------------------------------------------------------------- /internal/githubv4mock/objects_are_equal_values.go: -------------------------------------------------------------------------------- 1 | // The contents of this file are taken from https://github.com/stretchr/testify/blob/016e2e9c269209287f33ec203f340a9a723fe22c/assert/assertions.go#L166 2 | // because I do not want to take a dependency on the entire testify module just to use this equality check. 3 | // 4 | // There is a modification in objectsAreEqual to check that typed nils are equal, even if their types are different. 5 | // 6 | // The original license, copied from https://github.com/stretchr/testify/blob/016e2e9c269209287f33ec203f340a9a723fe22c/LICENSE 7 | // 8 | // MIT License 9 | // 10 | // Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors. 11 | 12 | // Permission is hereby granted, free of charge, to any person obtaining a copy 13 | // of this software and associated documentation files (the "Software"), to deal 14 | // in the Software without restriction, including without limitation the rights 15 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | // copies of the Software, and to permit persons to whom the Software is 17 | // furnished to do so, subject to the following conditions: 18 | 19 | // The above copyright notice and this permission notice shall be included in all 20 | // copies or substantial portions of the Software. 21 | 22 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | // SOFTWARE. 29 | package githubv4mock 30 | 31 | import ( 32 | "bytes" 33 | "reflect" 34 | ) 35 | 36 | func objectsAreEqualValues(expected, actual any) bool { 37 | if objectsAreEqual(expected, actual) { 38 | return true 39 | } 40 | 41 | expectedValue := reflect.ValueOf(expected) 42 | actualValue := reflect.ValueOf(actual) 43 | if !expectedValue.IsValid() || !actualValue.IsValid() { 44 | return false 45 | } 46 | 47 | expectedType := expectedValue.Type() 48 | actualType := actualValue.Type() 49 | if !expectedType.ConvertibleTo(actualType) { 50 | return false 51 | } 52 | 53 | if !isNumericType(expectedType) || !isNumericType(actualType) { 54 | // Attempt comparison after type conversion 55 | return reflect.DeepEqual( 56 | expectedValue.Convert(actualType).Interface(), actual, 57 | ) 58 | } 59 | 60 | // If BOTH values are numeric, there are chances of false positives due 61 | // to overflow or underflow. So, we need to make sure to always convert 62 | // the smaller type to a larger type before comparing. 63 | if expectedType.Size() >= actualType.Size() { 64 | return actualValue.Convert(expectedType).Interface() == expected 65 | } 66 | 67 | return expectedValue.Convert(actualType).Interface() == actual 68 | } 69 | 70 | // objectsAreEqual determines if two objects are considered equal. 71 | // 72 | // This function does no assertion of any kind. 73 | func objectsAreEqual(expected, actual any) bool { 74 | // There is a modification in objectsAreEqual to check that typed nils are equal, even if their types are different. 75 | // This is required because when a nil is provided as a variable, the type is not known. 76 | if isNil(expected) && isNil(actual) { 77 | return true 78 | } 79 | 80 | exp, ok := expected.([]byte) 81 | if !ok { 82 | return reflect.DeepEqual(expected, actual) 83 | } 84 | 85 | act, ok := actual.([]byte) 86 | if !ok { 87 | return false 88 | } 89 | if exp == nil || act == nil { 90 | return exp == nil && act == nil 91 | } 92 | return bytes.Equal(exp, act) 93 | } 94 | 95 | // isNumericType returns true if the type is one of: 96 | // int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, 97 | // float32, float64, complex64, complex128 98 | func isNumericType(t reflect.Type) bool { 99 | return t.Kind() >= reflect.Int && t.Kind() <= reflect.Complex128 100 | } 101 | 102 | func isNil(i any) bool { 103 | if i == nil { 104 | return true 105 | } 106 | v := reflect.ValueOf(i) 107 | switch v.Kind() { 108 | case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice: 109 | return v.IsNil() 110 | default: 111 | return false 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /internal/githubv4mock/objects_are_equal_values_test.go: -------------------------------------------------------------------------------- 1 | // The contents of this file are taken from https://github.com/stretchr/testify/blob/016e2e9c269209287f33ec203f340a9a723fe22c/assert/assertions_test.go#L140-L174 2 | // 3 | // There is a modification to test objectsAreEqualValues to check that typed nils are equal, even if their types are different. 4 | 5 | // The original license, copied from https://github.com/stretchr/testify/blob/016e2e9c269209287f33ec203f340a9a723fe22c/LICENSE 6 | // 7 | // MIT License 8 | // 9 | // Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors. 10 | 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | package githubv4mock 29 | 30 | import ( 31 | "fmt" 32 | "math" 33 | "testing" 34 | "time" 35 | ) 36 | 37 | func TestObjectsAreEqualValues(t *testing.T) { 38 | now := time.Now() 39 | 40 | cases := []struct { 41 | expected interface{} 42 | actual interface{} 43 | result bool 44 | }{ 45 | {uint32(10), int32(10), true}, 46 | {0, nil, false}, 47 | {nil, 0, false}, 48 | {now, now.In(time.Local), false}, // should not be time zone independent 49 | {int(270), int8(14), false}, // should handle overflow/underflow 50 | {int8(14), int(270), false}, 51 | {[]int{270, 270}, []int8{14, 14}, false}, 52 | {complex128(1e+100 + 1e+100i), complex64(complex(math.Inf(0), math.Inf(0))), false}, 53 | {complex64(complex(math.Inf(0), math.Inf(0))), complex128(1e+100 + 1e+100i), false}, 54 | {complex128(1e+100 + 1e+100i), 270, false}, 55 | {270, complex128(1e+100 + 1e+100i), false}, 56 | {complex128(1e+100 + 1e+100i), 3.14, false}, 57 | {3.14, complex128(1e+100 + 1e+100i), false}, 58 | {complex128(1e+10 + 1e+10i), complex64(1e+10 + 1e+10i), true}, 59 | {complex64(1e+10 + 1e+10i), complex128(1e+10 + 1e+10i), true}, 60 | {(*string)(nil), nil, true}, // typed nil vs untyped nil 61 | {(*string)(nil), (*int)(nil), true}, // different typed nils 62 | } 63 | 64 | for _, c := range cases { 65 | t.Run(fmt.Sprintf("ObjectsAreEqualValues(%#v, %#v)", c.expected, c.actual), func(t *testing.T) { 66 | res := objectsAreEqualValues(c.expected, c.actual) 67 | 68 | if res != c.result { 69 | t.Errorf("ObjectsAreEqualValues(%#v, %#v) should return %#v", c.expected, c.actual, c.result) 70 | } 71 | }) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /internal/githubv4mock/query.go: -------------------------------------------------------------------------------- 1 | // Ths contents of this file are taken from https://github.com/shurcooL/graphql/blob/ed46e5a4646634fc16cb07c3b8db389542cc8847/query.go 2 | // because they are not exported by the module, and we would like to use them in building the githubv4mock test utility. 3 | // 4 | // The original license, copied from https://github.com/shurcooL/graphql/blob/ed46e5a4646634fc16cb07c3b8db389542cc8847/LICENSE 5 | // 6 | // MIT License 7 | 8 | // Copyright (c) 2017 Dmitri Shuralyov 9 | 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | package githubv4mock 28 | 29 | import ( 30 | "bytes" 31 | "encoding/json" 32 | "io" 33 | "reflect" 34 | "sort" 35 | 36 | "github.com/shurcooL/graphql/ident" 37 | ) 38 | 39 | func constructQuery(v any, variables map[string]any) string { 40 | query := query(v) 41 | if len(variables) > 0 { 42 | return "query(" + queryArguments(variables) + ")" + query 43 | } 44 | return query 45 | } 46 | 47 | func constructMutation(v any, variables map[string]any) string { 48 | query := query(v) 49 | if len(variables) > 0 { 50 | return "mutation(" + queryArguments(variables) + ")" + query 51 | } 52 | return "mutation" + query 53 | } 54 | 55 | // queryArguments constructs a minified arguments string for variables. 56 | // 57 | // E.g., map[string]any{"a": Int(123), "b": NewBoolean(true)} -> "$a:Int!$b:Boolean". 58 | func queryArguments(variables map[string]any) string { 59 | // Sort keys in order to produce deterministic output for testing purposes. 60 | // TODO: If tests can be made to work with non-deterministic output, then no need to sort. 61 | keys := make([]string, 0, len(variables)) 62 | for k := range variables { 63 | keys = append(keys, k) 64 | } 65 | sort.Strings(keys) 66 | 67 | var buf bytes.Buffer 68 | for _, k := range keys { 69 | _, _ = io.WriteString(&buf, "$") 70 | _, _ = io.WriteString(&buf, k) 71 | _, _ = io.WriteString(&buf, ":") 72 | writeArgumentType(&buf, reflect.TypeOf(variables[k]), true) 73 | // Don't insert a comma here. 74 | // Commas in GraphQL are insignificant, and we want minified output. 75 | // See https://spec.graphql.org/October2021/#sec-Insignificant-Commas. 76 | } 77 | return buf.String() 78 | } 79 | 80 | // writeArgumentType writes a minified GraphQL type for t to w. 81 | // value indicates whether t is a value (required) type or pointer (optional) type. 82 | // If value is true, then "!" is written at the end of t. 83 | func writeArgumentType(w io.Writer, t reflect.Type, value bool) { 84 | if t.Kind() == reflect.Ptr { 85 | // Pointer is an optional type, so no "!" at the end of the pointer's underlying type. 86 | writeArgumentType(w, t.Elem(), false) 87 | return 88 | } 89 | 90 | switch t.Kind() { 91 | case reflect.Slice, reflect.Array: 92 | // List. E.g., "[Int]". 93 | _, _ = io.WriteString(w, "[") 94 | writeArgumentType(w, t.Elem(), true) 95 | _, _ = io.WriteString(w, "]") 96 | default: 97 | // Named type. E.g., "Int". 98 | name := t.Name() 99 | if name == "string" { // HACK: Workaround for https://github.com/shurcooL/githubv4/issues/12. 100 | name = "ID" 101 | } 102 | _, _ = io.WriteString(w, name) 103 | } 104 | 105 | if value { 106 | // Value is a required type, so add "!" to the end. 107 | _, _ = io.WriteString(w, "!") 108 | } 109 | } 110 | 111 | // query uses writeQuery to recursively construct 112 | // a minified query string from the provided struct v. 113 | // 114 | // E.g., struct{Foo Int, BarBaz *Boolean} -> "{foo,barBaz}". 115 | func query(v any) string { 116 | var buf bytes.Buffer 117 | writeQuery(&buf, reflect.TypeOf(v), false) 118 | return buf.String() 119 | } 120 | 121 | // writeQuery writes a minified query for t to w. 122 | // If inline is true, the struct fields of t are inlined into parent struct. 123 | func writeQuery(w io.Writer, t reflect.Type, inline bool) { 124 | switch t.Kind() { 125 | case reflect.Ptr, reflect.Slice: 126 | writeQuery(w, t.Elem(), false) 127 | case reflect.Struct: 128 | // If the type implements json.Unmarshaler, it's a scalar. Don't expand it. 129 | if reflect.PointerTo(t).Implements(jsonUnmarshaler) { 130 | return 131 | } 132 | if !inline { 133 | _, _ = io.WriteString(w, "{") 134 | } 135 | for i := 0; i < t.NumField(); i++ { 136 | if i != 0 { 137 | _, _ = io.WriteString(w, ",") 138 | } 139 | f := t.Field(i) 140 | value, ok := f.Tag.Lookup("graphql") 141 | inlineField := f.Anonymous && !ok 142 | if !inlineField { 143 | if ok { 144 | _, _ = io.WriteString(w, value) 145 | } else { 146 | _, _ = io.WriteString(w, ident.ParseMixedCaps(f.Name).ToLowerCamelCase()) 147 | } 148 | } 149 | writeQuery(w, f.Type, inlineField) 150 | } 151 | if !inline { 152 | _, _ = io.WriteString(w, "}") 153 | } 154 | } 155 | } 156 | 157 | var jsonUnmarshaler = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem() 158 | -------------------------------------------------------------------------------- /internal/toolsnaps/toolsnaps.go: -------------------------------------------------------------------------------- 1 | // Package toolsnaps provides test utilities for ensuring json schemas for tools 2 | // have not changed unexpectedly. 3 | package toolsnaps 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | "os" 9 | "path/filepath" 10 | 11 | "github.com/josephburnett/jd/v2" 12 | ) 13 | 14 | // Test checks that the JSON schema for a tool has not changed unexpectedly. 15 | // It compares the marshaled JSON of the provided tool against a stored snapshot file. 16 | // If the UPDATE_TOOLSNAPS environment variable is set to "true", it updates the snapshot file instead. 17 | // If the snapshot does not exist and not running in CI, it creates the snapshot file. 18 | // If the snapshot does not exist and running in CI (GITHUB_ACTIONS="true"), it returns an error. 19 | // If the snapshot exists, it compares the tool's JSON to the snapshot and returns an error if they differ. 20 | // Returns an error if marshaling, reading, or comparing fails. 21 | func Test(toolName string, tool any) error { 22 | toolJSON, err := json.MarshalIndent(tool, "", " ") 23 | if err != nil { 24 | return fmt.Errorf("failed to marshal tool %s: %w", toolName, err) 25 | } 26 | 27 | snapPath := fmt.Sprintf("__toolsnaps__/%s.snap", toolName) 28 | 29 | // If UPDATE_TOOLSNAPS is set, then we write the tool JSON to the snapshot file and exit 30 | if os.Getenv("UPDATE_TOOLSNAPS") == "true" { 31 | return writeSnap(snapPath, toolJSON) 32 | } 33 | 34 | snapJSON, err := os.ReadFile(snapPath) //nolint:gosec // filepaths are controlled by the test suite, so this is safe. 35 | // If the snapshot file does not exist, this must be the first time this test is run. 36 | // We write the tool JSON to the snapshot file and exit. 37 | if os.IsNotExist(err) { 38 | // If we're running in CI, we will error if there is not snapshot because it's important that snapshots 39 | // are committed alongside the tests, rather than just being constructed and not committed during a CI run. 40 | if os.Getenv("GITHUB_ACTIONS") == "true" { 41 | return fmt.Errorf("tool snapshot does not exist for %s. Please run the tests with UPDATE_TOOLSNAPS=true to create it", toolName) 42 | } 43 | 44 | return writeSnap(snapPath, toolJSON) 45 | } 46 | 47 | // Otherwise we will compare the tool JSON to the snapshot JSON 48 | toolNode, err := jd.ReadJsonString(string(toolJSON)) 49 | if err != nil { 50 | return fmt.Errorf("failed to parse tool JSON for %s: %w", toolName, err) 51 | } 52 | 53 | snapNode, err := jd.ReadJsonString(string(snapJSON)) 54 | if err != nil { 55 | return fmt.Errorf("failed to parse snapshot JSON for %s: %w", toolName, err) 56 | } 57 | 58 | // jd.Set allows arrays to be compared without order sensitivity, 59 | // which is useful because we don't really care about this when exposing tool schemas. 60 | diff := toolNode.Diff(snapNode, jd.SET).Render() 61 | if diff != "" { 62 | // If there is a difference, we return an error with the diff 63 | return fmt.Errorf("tool schema for %s has changed unexpectedly:\n%s", toolName, diff) 64 | } 65 | 66 | return nil 67 | } 68 | 69 | func writeSnap(snapPath string, contents []byte) error { 70 | // Ensure the directory exists 71 | if err := os.MkdirAll(filepath.Dir(snapPath), 0700); err != nil { 72 | return fmt.Errorf("failed to create snapshot directory: %w", err) 73 | } 74 | 75 | // Write the snapshot file 76 | if err := os.WriteFile(snapPath, contents, 0600); err != nil { 77 | return fmt.Errorf("failed to write snapshot file: %w", err) 78 | } 79 | 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /internal/toolsnaps/toolsnaps_test.go: -------------------------------------------------------------------------------- 1 | package toolsnaps 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | type dummyTool struct { 14 | Name string `json:"name"` 15 | Value int `json:"value"` 16 | } 17 | 18 | // withIsolatedWorkingDir creates a temp dir, changes to it, and restores the original working dir after the test. 19 | func withIsolatedWorkingDir(t *testing.T) { 20 | dir := t.TempDir() 21 | origDir, err := os.Getwd() 22 | require.NoError(t, err) 23 | t.Cleanup(func() { require.NoError(t, os.Chdir(origDir)) }) 24 | require.NoError(t, os.Chdir(dir)) 25 | } 26 | 27 | func TestSnapshotDoesNotExistNotInCI(t *testing.T) { 28 | withIsolatedWorkingDir(t) 29 | 30 | // Given we are not running in CI 31 | t.Setenv("GITHUB_ACTIONS", "false") // This REALLY is required because the tests run in CI 32 | tool := dummyTool{"foo", 42} 33 | 34 | // When we test the snapshot 35 | err := Test("dummy", tool) 36 | 37 | // Then it should succeed and write the snapshot file 38 | require.NoError(t, err) 39 | path := filepath.Join("__toolsnaps__", "dummy.snap") 40 | _, statErr := os.Stat(path) 41 | assert.NoError(t, statErr, "expected snapshot file to be written") 42 | } 43 | 44 | func TestSnapshotDoesNotExistInCI(t *testing.T) { 45 | withIsolatedWorkingDir(t) 46 | 47 | // Given we are running in CI 48 | t.Setenv("GITHUB_ACTIONS", "true") 49 | tool := dummyTool{"foo", 42} 50 | 51 | // When we test the snapshot 52 | err := Test("dummy", tool) 53 | 54 | // Then it should error about missing snapshot in CI 55 | require.Error(t, err) 56 | assert.Contains(t, err.Error(), "tool snapshot does not exist", "expected error about missing snapshot in CI") 57 | } 58 | 59 | func TestSnapshotExistsMatch(t *testing.T) { 60 | withIsolatedWorkingDir(t) 61 | 62 | // Given a matching snapshot file exists 63 | tool := dummyTool{"foo", 42} 64 | b, _ := json.MarshalIndent(tool, "", " ") 65 | require.NoError(t, os.MkdirAll("__toolsnaps__", 0700)) 66 | require.NoError(t, os.WriteFile(filepath.Join("__toolsnaps__", "dummy.snap"), b, 0600)) 67 | 68 | // When we test the snapshot 69 | err := Test("dummy", tool) 70 | 71 | // Then it should succeed (no error) 72 | require.NoError(t, err) 73 | } 74 | 75 | func TestSnapshotExistsDiff(t *testing.T) { 76 | withIsolatedWorkingDir(t) 77 | 78 | // Given a non-matching snapshot file exists 79 | require.NoError(t, os.MkdirAll("__toolsnaps__", 0700)) 80 | require.NoError(t, os.WriteFile(filepath.Join("__toolsnaps__", "dummy.snap"), []byte(`{"name":"foo","value":1}`), 0600)) 81 | tool := dummyTool{"foo", 2} 82 | 83 | // When we test the snapshot 84 | err := Test("dummy", tool) 85 | 86 | // Then it should error about the schema diff 87 | require.Error(t, err) 88 | assert.Contains(t, err.Error(), "tool schema for dummy has changed unexpectedly", "expected error about diff") 89 | } 90 | 91 | func TestUpdateToolsnaps(t *testing.T) { 92 | withIsolatedWorkingDir(t) 93 | 94 | // Given UPDATE_TOOLSNAPS is set, regardless of whether a matching snapshot file exists 95 | t.Setenv("UPDATE_TOOLSNAPS", "true") 96 | require.NoError(t, os.MkdirAll("__toolsnaps__", 0700)) 97 | require.NoError(t, os.WriteFile(filepath.Join("__toolsnaps__", "dummy.snap"), []byte(`{"name":"foo","value":1}`), 0600)) 98 | tool := dummyTool{"foo", 42} 99 | 100 | // When we test the snapshot 101 | err := Test("dummy", tool) 102 | 103 | // Then it should succeed and write the snapshot file 104 | require.NoError(t, err) 105 | path := filepath.Join("__toolsnaps__", "dummy.snap") 106 | _, statErr := os.Stat(path) 107 | assert.NoError(t, statErr, "expected snapshot file to be written") 108 | } 109 | 110 | func TestMalformedSnapshotJSON(t *testing.T) { 111 | withIsolatedWorkingDir(t) 112 | 113 | // Given a malformed snapshot file exists 114 | require.NoError(t, os.MkdirAll("__toolsnaps__", 0700)) 115 | require.NoError(t, os.WriteFile(filepath.Join("__toolsnaps__", "dummy.snap"), []byte(`not-json`), 0600)) 116 | tool := dummyTool{"foo", 42} 117 | 118 | // When we test the snapshot 119 | err := Test("dummy", tool) 120 | 121 | // Then it should error about malformed snapshot JSON 122 | require.Error(t, err) 123 | assert.Contains(t, err.Error(), "failed to parse snapshot JSON for dummy", "expected error about malformed snapshot JSON") 124 | } 125 | -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/get_me.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Get my user profile", 4 | "readOnlyHint": true 5 | }, 6 | "description": "Get details of the authenticated GitHub user. Use this when a request includes \"me\", \"my\". The output will not change unless the user changes their profile, so only call this once.", 7 | "inputSchema": { 8 | "properties": { 9 | "reason": { 10 | "description": "Optional: the reason for requesting the user information", 11 | "type": "string" 12 | } 13 | }, 14 | "type": "object" 15 | }, 16 | "name": "get_me" 17 | } -------------------------------------------------------------------------------- /pkg/github/code_scanning.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | 10 | "github.com/github/github-mcp-server/pkg/translations" 11 | "github.com/google/go-github/v69/github" 12 | "github.com/mark3labs/mcp-go/mcp" 13 | "github.com/mark3labs/mcp-go/server" 14 | ) 15 | 16 | func GetCodeScanningAlert(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { 17 | return mcp.NewTool("get_code_scanning_alert", 18 | mcp.WithDescription(t("TOOL_GET_CODE_SCANNING_ALERT_DESCRIPTION", "Get details of a specific code scanning alert in a GitHub repository.")), 19 | mcp.WithToolAnnotation(mcp.ToolAnnotation{ 20 | Title: t("TOOL_GET_CODE_SCANNING_ALERT_USER_TITLE", "Get code scanning alert"), 21 | ReadOnlyHint: toBoolPtr(true), 22 | }), 23 | mcp.WithString("owner", 24 | mcp.Required(), 25 | mcp.Description("The owner of the repository."), 26 | ), 27 | mcp.WithString("repo", 28 | mcp.Required(), 29 | mcp.Description("The name of the repository."), 30 | ), 31 | mcp.WithNumber("alertNumber", 32 | mcp.Required(), 33 | mcp.Description("The number of the alert."), 34 | ), 35 | ), 36 | func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 37 | owner, err := requiredParam[string](request, "owner") 38 | if err != nil { 39 | return mcp.NewToolResultError(err.Error()), nil 40 | } 41 | repo, err := requiredParam[string](request, "repo") 42 | if err != nil { 43 | return mcp.NewToolResultError(err.Error()), nil 44 | } 45 | alertNumber, err := RequiredInt(request, "alertNumber") 46 | if err != nil { 47 | return mcp.NewToolResultError(err.Error()), nil 48 | } 49 | 50 | client, err := getClient(ctx) 51 | if err != nil { 52 | return nil, fmt.Errorf("failed to get GitHub client: %w", err) 53 | } 54 | 55 | alert, resp, err := client.CodeScanning.GetAlert(ctx, owner, repo, int64(alertNumber)) 56 | if err != nil { 57 | return nil, fmt.Errorf("failed to get alert: %w", err) 58 | } 59 | defer func() { _ = resp.Body.Close() }() 60 | 61 | if resp.StatusCode != http.StatusOK { 62 | body, err := io.ReadAll(resp.Body) 63 | if err != nil { 64 | return nil, fmt.Errorf("failed to read response body: %w", err) 65 | } 66 | return mcp.NewToolResultError(fmt.Sprintf("failed to get alert: %s", string(body))), nil 67 | } 68 | 69 | r, err := json.Marshal(alert) 70 | if err != nil { 71 | return nil, fmt.Errorf("failed to marshal alert: %w", err) 72 | } 73 | 74 | return mcp.NewToolResultText(string(r)), nil 75 | } 76 | } 77 | 78 | func ListCodeScanningAlerts(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { 79 | return mcp.NewTool("list_code_scanning_alerts", 80 | mcp.WithDescription(t("TOOL_LIST_CODE_SCANNING_ALERTS_DESCRIPTION", "List code scanning alerts in a GitHub repository.")), 81 | mcp.WithToolAnnotation(mcp.ToolAnnotation{ 82 | Title: t("TOOL_LIST_CODE_SCANNING_ALERTS_USER_TITLE", "List code scanning alerts"), 83 | ReadOnlyHint: toBoolPtr(true), 84 | }), 85 | mcp.WithString("owner", 86 | mcp.Required(), 87 | mcp.Description("The owner of the repository."), 88 | ), 89 | mcp.WithString("repo", 90 | mcp.Required(), 91 | mcp.Description("The name of the repository."), 92 | ), 93 | mcp.WithString("ref", 94 | mcp.Description("The Git reference for the results you want to list."), 95 | ), 96 | mcp.WithString("state", 97 | mcp.Description("Filter code scanning alerts by state. Defaults to open"), 98 | mcp.DefaultString("open"), 99 | mcp.Enum("open", "closed", "dismissed", "fixed"), 100 | ), 101 | mcp.WithString("severity", 102 | mcp.Description("Filter code scanning alerts by severity"), 103 | mcp.Enum("critical", "high", "medium", "low", "warning", "note", "error"), 104 | ), 105 | mcp.WithString("tool_name", 106 | mcp.Description("The name of the tool used for code scanning."), 107 | ), 108 | ), 109 | func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 110 | owner, err := requiredParam[string](request, "owner") 111 | if err != nil { 112 | return mcp.NewToolResultError(err.Error()), nil 113 | } 114 | repo, err := requiredParam[string](request, "repo") 115 | if err != nil { 116 | return mcp.NewToolResultError(err.Error()), nil 117 | } 118 | ref, err := OptionalParam[string](request, "ref") 119 | if err != nil { 120 | return mcp.NewToolResultError(err.Error()), nil 121 | } 122 | state, err := OptionalParam[string](request, "state") 123 | if err != nil { 124 | return mcp.NewToolResultError(err.Error()), nil 125 | } 126 | severity, err := OptionalParam[string](request, "severity") 127 | if err != nil { 128 | return mcp.NewToolResultError(err.Error()), nil 129 | } 130 | toolName, err := OptionalParam[string](request, "tool_name") 131 | if err != nil { 132 | return mcp.NewToolResultError(err.Error()), nil 133 | } 134 | 135 | client, err := getClient(ctx) 136 | if err != nil { 137 | return nil, fmt.Errorf("failed to get GitHub client: %w", err) 138 | } 139 | alerts, resp, err := client.CodeScanning.ListAlertsForRepo(ctx, owner, repo, &github.AlertListOptions{Ref: ref, State: state, Severity: severity, ToolName: toolName}) 140 | if err != nil { 141 | return nil, fmt.Errorf("failed to list alerts: %w", err) 142 | } 143 | defer func() { _ = resp.Body.Close() }() 144 | 145 | if resp.StatusCode != http.StatusOK { 146 | body, err := io.ReadAll(resp.Body) 147 | if err != nil { 148 | return nil, fmt.Errorf("failed to read response body: %w", err) 149 | } 150 | return mcp.NewToolResultError(fmt.Sprintf("failed to list alerts: %s", string(body))), nil 151 | } 152 | 153 | r, err := json.Marshal(alerts) 154 | if err != nil { 155 | return nil, fmt.Errorf("failed to marshal alerts: %w", err) 156 | } 157 | 158 | return mcp.NewToolResultText(string(r)), nil 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /pkg/github/code_scanning_test.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | "testing" 8 | 9 | "github.com/github/github-mcp-server/pkg/translations" 10 | "github.com/google/go-github/v69/github" 11 | "github.com/migueleliasweb/go-github-mock/src/mock" 12 | "github.com/stretchr/testify/assert" 13 | "github.com/stretchr/testify/require" 14 | ) 15 | 16 | func Test_GetCodeScanningAlert(t *testing.T) { 17 | // Verify tool definition once 18 | mockClient := github.NewClient(nil) 19 | tool, _ := GetCodeScanningAlert(stubGetClientFn(mockClient), translations.NullTranslationHelper) 20 | 21 | assert.Equal(t, "get_code_scanning_alert", tool.Name) 22 | assert.NotEmpty(t, tool.Description) 23 | assert.Contains(t, tool.InputSchema.Properties, "owner") 24 | assert.Contains(t, tool.InputSchema.Properties, "repo") 25 | assert.Contains(t, tool.InputSchema.Properties, "alertNumber") 26 | assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner", "repo", "alertNumber"}) 27 | 28 | // Setup mock alert for success case 29 | mockAlert := &github.Alert{ 30 | Number: github.Ptr(42), 31 | State: github.Ptr("open"), 32 | Rule: &github.Rule{ID: github.Ptr("test-rule"), Description: github.Ptr("Test Rule Description")}, 33 | HTMLURL: github.Ptr("https://github.com/owner/repo/security/code-scanning/42"), 34 | } 35 | 36 | tests := []struct { 37 | name string 38 | mockedClient *http.Client 39 | requestArgs map[string]interface{} 40 | expectError bool 41 | expectedAlert *github.Alert 42 | expectedErrMsg string 43 | }{ 44 | { 45 | name: "successful alert fetch", 46 | mockedClient: mock.NewMockedHTTPClient( 47 | mock.WithRequestMatch( 48 | mock.GetReposCodeScanningAlertsByOwnerByRepoByAlertNumber, 49 | mockAlert, 50 | ), 51 | ), 52 | requestArgs: map[string]interface{}{ 53 | "owner": "owner", 54 | "repo": "repo", 55 | "alertNumber": float64(42), 56 | }, 57 | expectError: false, 58 | expectedAlert: mockAlert, 59 | }, 60 | { 61 | name: "alert fetch fails", 62 | mockedClient: mock.NewMockedHTTPClient( 63 | mock.WithRequestMatchHandler( 64 | mock.GetReposCodeScanningAlertsByOwnerByRepoByAlertNumber, 65 | http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 66 | w.WriteHeader(http.StatusNotFound) 67 | _, _ = w.Write([]byte(`{"message": "Not Found"}`)) 68 | }), 69 | ), 70 | ), 71 | requestArgs: map[string]interface{}{ 72 | "owner": "owner", 73 | "repo": "repo", 74 | "alertNumber": float64(9999), 75 | }, 76 | expectError: true, 77 | expectedErrMsg: "failed to get alert", 78 | }, 79 | } 80 | 81 | for _, tc := range tests { 82 | t.Run(tc.name, func(t *testing.T) { 83 | // Setup client with mock 84 | client := github.NewClient(tc.mockedClient) 85 | _, handler := GetCodeScanningAlert(stubGetClientFn(client), translations.NullTranslationHelper) 86 | 87 | // Create call request 88 | request := createMCPRequest(tc.requestArgs) 89 | 90 | // Call handler 91 | result, err := handler(context.Background(), request) 92 | 93 | // Verify results 94 | if tc.expectError { 95 | require.Error(t, err) 96 | assert.Contains(t, err.Error(), tc.expectedErrMsg) 97 | return 98 | } 99 | 100 | require.NoError(t, err) 101 | 102 | // Parse the result and get the text content if no error 103 | textContent := getTextResult(t, result) 104 | 105 | // Unmarshal and verify the result 106 | var returnedAlert github.Alert 107 | err = json.Unmarshal([]byte(textContent.Text), &returnedAlert) 108 | assert.NoError(t, err) 109 | assert.Equal(t, *tc.expectedAlert.Number, *returnedAlert.Number) 110 | assert.Equal(t, *tc.expectedAlert.State, *returnedAlert.State) 111 | assert.Equal(t, *tc.expectedAlert.Rule.ID, *returnedAlert.Rule.ID) 112 | assert.Equal(t, *tc.expectedAlert.HTMLURL, *returnedAlert.HTMLURL) 113 | 114 | }) 115 | } 116 | } 117 | 118 | func Test_ListCodeScanningAlerts(t *testing.T) { 119 | // Verify tool definition once 120 | mockClient := github.NewClient(nil) 121 | tool, _ := ListCodeScanningAlerts(stubGetClientFn(mockClient), translations.NullTranslationHelper) 122 | 123 | assert.Equal(t, "list_code_scanning_alerts", tool.Name) 124 | assert.NotEmpty(t, tool.Description) 125 | assert.Contains(t, tool.InputSchema.Properties, "owner") 126 | assert.Contains(t, tool.InputSchema.Properties, "repo") 127 | assert.Contains(t, tool.InputSchema.Properties, "ref") 128 | assert.Contains(t, tool.InputSchema.Properties, "state") 129 | assert.Contains(t, tool.InputSchema.Properties, "severity") 130 | assert.Contains(t, tool.InputSchema.Properties, "tool_name") 131 | assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner", "repo"}) 132 | 133 | // Setup mock alerts for success case 134 | mockAlerts := []*github.Alert{ 135 | { 136 | Number: github.Ptr(42), 137 | State: github.Ptr("open"), 138 | Rule: &github.Rule{ID: github.Ptr("test-rule-1"), Description: github.Ptr("Test Rule 1")}, 139 | HTMLURL: github.Ptr("https://github.com/owner/repo/security/code-scanning/42"), 140 | }, 141 | { 142 | Number: github.Ptr(43), 143 | State: github.Ptr("fixed"), 144 | Rule: &github.Rule{ID: github.Ptr("test-rule-2"), Description: github.Ptr("Test Rule 2")}, 145 | HTMLURL: github.Ptr("https://github.com/owner/repo/security/code-scanning/43"), 146 | }, 147 | } 148 | 149 | tests := []struct { 150 | name string 151 | mockedClient *http.Client 152 | requestArgs map[string]interface{} 153 | expectError bool 154 | expectedAlerts []*github.Alert 155 | expectedErrMsg string 156 | }{ 157 | { 158 | name: "successful alerts listing", 159 | mockedClient: mock.NewMockedHTTPClient( 160 | mock.WithRequestMatchHandler( 161 | mock.GetReposCodeScanningAlertsByOwnerByRepo, 162 | expectQueryParams(t, map[string]string{ 163 | "ref": "main", 164 | "state": "open", 165 | "severity": "high", 166 | "tool_name": "codeql", 167 | }).andThen( 168 | mockResponse(t, http.StatusOK, mockAlerts), 169 | ), 170 | ), 171 | ), 172 | requestArgs: map[string]interface{}{ 173 | "owner": "owner", 174 | "repo": "repo", 175 | "ref": "main", 176 | "state": "open", 177 | "severity": "high", 178 | "tool_name": "codeql", 179 | }, 180 | expectError: false, 181 | expectedAlerts: mockAlerts, 182 | }, 183 | { 184 | name: "alerts listing fails", 185 | mockedClient: mock.NewMockedHTTPClient( 186 | mock.WithRequestMatchHandler( 187 | mock.GetReposCodeScanningAlertsByOwnerByRepo, 188 | http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 189 | w.WriteHeader(http.StatusUnauthorized) 190 | _, _ = w.Write([]byte(`{"message": "Unauthorized access"}`)) 191 | }), 192 | ), 193 | ), 194 | requestArgs: map[string]interface{}{ 195 | "owner": "owner", 196 | "repo": "repo", 197 | }, 198 | expectError: true, 199 | expectedErrMsg: "failed to list alerts", 200 | }, 201 | } 202 | 203 | for _, tc := range tests { 204 | t.Run(tc.name, func(t *testing.T) { 205 | // Setup client with mock 206 | client := github.NewClient(tc.mockedClient) 207 | _, handler := ListCodeScanningAlerts(stubGetClientFn(client), translations.NullTranslationHelper) 208 | 209 | // Create call request 210 | request := createMCPRequest(tc.requestArgs) 211 | 212 | // Call handler 213 | result, err := handler(context.Background(), request) 214 | 215 | // Verify results 216 | if tc.expectError { 217 | require.Error(t, err) 218 | assert.Contains(t, err.Error(), tc.expectedErrMsg) 219 | return 220 | } 221 | 222 | require.NoError(t, err) 223 | 224 | // Parse the result and get the text content if no error 225 | textContent := getTextResult(t, result) 226 | 227 | // Unmarshal and verify the result 228 | var returnedAlerts []*github.Alert 229 | err = json.Unmarshal([]byte(textContent.Text), &returnedAlerts) 230 | assert.NoError(t, err) 231 | assert.Len(t, returnedAlerts, len(tc.expectedAlerts)) 232 | for i, alert := range returnedAlerts { 233 | assert.Equal(t, *tc.expectedAlerts[i].Number, *alert.Number) 234 | assert.Equal(t, *tc.expectedAlerts[i].State, *alert.State) 235 | assert.Equal(t, *tc.expectedAlerts[i].Rule.ID, *alert.Rule.ID) 236 | assert.Equal(t, *tc.expectedAlerts[i].HTMLURL, *alert.HTMLURL) 237 | } 238 | }) 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /pkg/github/context_tools.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/github/github-mcp-server/pkg/translations" 7 | "github.com/mark3labs/mcp-go/mcp" 8 | "github.com/mark3labs/mcp-go/server" 9 | ) 10 | 11 | // GetMe creates a tool to get details of the authenticated user. 12 | func GetMe(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, server.ToolHandlerFunc) { 13 | tool := mcp.NewTool("get_me", 14 | mcp.WithDescription(t("TOOL_GET_ME_DESCRIPTION", "Get details of the authenticated GitHub user. Use this when a request includes \"me\", \"my\". The output will not change unless the user changes their profile, so only call this once.")), 15 | mcp.WithToolAnnotation(mcp.ToolAnnotation{ 16 | Title: t("TOOL_GET_ME_USER_TITLE", "Get my user profile"), 17 | ReadOnlyHint: toBoolPtr(true), 18 | }), 19 | mcp.WithString("reason", 20 | mcp.Description("Optional: the reason for requesting the user information"), 21 | ), 22 | ) 23 | 24 | type args struct{} 25 | handler := mcp.NewTypedToolHandler(func(ctx context.Context, _ mcp.CallToolRequest, _ args) (*mcp.CallToolResult, error) { 26 | client, err := getClient(ctx) 27 | if err != nil { 28 | return mcp.NewToolResultErrorFromErr("failed to get GitHub client", err), nil 29 | } 30 | 31 | user, _, err := client.Users.Get(ctx, "") 32 | if err != nil { 33 | return mcp.NewToolResultErrorFromErr("failed to get user", err), nil 34 | } 35 | 36 | return MarshalledTextResult(user), nil 37 | }) 38 | 39 | return tool, handler 40 | } 41 | -------------------------------------------------------------------------------- /pkg/github/context_tools_test.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "testing" 7 | "time" 8 | 9 | "github.com/github/github-mcp-server/internal/toolsnaps" 10 | "github.com/github/github-mcp-server/pkg/translations" 11 | "github.com/google/go-github/v69/github" 12 | "github.com/migueleliasweb/go-github-mock/src/mock" 13 | "github.com/stretchr/testify/assert" 14 | "github.com/stretchr/testify/require" 15 | ) 16 | 17 | func Test_GetMe(t *testing.T) { 18 | t.Parallel() 19 | 20 | tool, _ := GetMe(nil, translations.NullTranslationHelper) 21 | require.NoError(t, toolsnaps.Test(tool.Name, tool)) 22 | 23 | // Verify some basic very important properties 24 | assert.Equal(t, "get_me", tool.Name) 25 | assert.True(t, *tool.Annotations.ReadOnlyHint, "get_me tool should be read-only") 26 | 27 | // Setup mock user response 28 | mockUser := &github.User{ 29 | Login: github.Ptr("testuser"), 30 | Name: github.Ptr("Test User"), 31 | Email: github.Ptr("test@example.com"), 32 | Bio: github.Ptr("GitHub user for testing"), 33 | Company: github.Ptr("Test Company"), 34 | Location: github.Ptr("Test Location"), 35 | HTMLURL: github.Ptr("https://github.com/testuser"), 36 | CreatedAt: &github.Timestamp{Time: time.Now().Add(-365 * 24 * time.Hour)}, 37 | Type: github.Ptr("User"), 38 | Plan: &github.Plan{ 39 | Name: github.Ptr("pro"), 40 | }, 41 | } 42 | 43 | tests := []struct { 44 | name string 45 | stubbedGetClientFn GetClientFn 46 | requestArgs map[string]any 47 | expectToolError bool 48 | expectedUser *github.User 49 | expectedToolErrMsg string 50 | }{ 51 | { 52 | name: "successful get user", 53 | stubbedGetClientFn: stubGetClientFromHTTPFn( 54 | mock.NewMockedHTTPClient( 55 | mock.WithRequestMatch( 56 | mock.GetUser, 57 | mockUser, 58 | ), 59 | ), 60 | ), 61 | requestArgs: map[string]any{}, 62 | expectToolError: false, 63 | expectedUser: mockUser, 64 | }, 65 | { 66 | name: "successful get user with reason", 67 | stubbedGetClientFn: stubGetClientFromHTTPFn( 68 | mock.NewMockedHTTPClient( 69 | mock.WithRequestMatch( 70 | mock.GetUser, 71 | mockUser, 72 | ), 73 | ), 74 | ), 75 | requestArgs: map[string]any{ 76 | "reason": "Testing API", 77 | }, 78 | expectToolError: false, 79 | expectedUser: mockUser, 80 | }, 81 | { 82 | name: "getting client fails", 83 | stubbedGetClientFn: stubGetClientFnErr("expected test error"), 84 | requestArgs: map[string]any{}, 85 | expectToolError: true, 86 | expectedToolErrMsg: "failed to get GitHub client: expected test error", 87 | }, 88 | { 89 | name: "get user fails", 90 | stubbedGetClientFn: stubGetClientFromHTTPFn( 91 | mock.NewMockedHTTPClient( 92 | mock.WithRequestMatchHandler( 93 | mock.GetUser, 94 | badRequestHandler("expected test failure"), 95 | ), 96 | ), 97 | ), 98 | requestArgs: map[string]any{}, 99 | expectToolError: true, 100 | expectedToolErrMsg: "expected test failure", 101 | }, 102 | } 103 | 104 | for _, tc := range tests { 105 | t.Run(tc.name, func(t *testing.T) { 106 | _, handler := GetMe(tc.stubbedGetClientFn, translations.NullTranslationHelper) 107 | 108 | request := createMCPRequest(tc.requestArgs) 109 | result, err := handler(context.Background(), request) 110 | require.NoError(t, err) 111 | textContent := getTextResult(t, result) 112 | 113 | if tc.expectToolError { 114 | assert.True(t, result.IsError, "expected tool call result to be an error") 115 | assert.Contains(t, textContent.Text, tc.expectedToolErrMsg) 116 | return 117 | } 118 | 119 | // Unmarshal and verify the result 120 | var returnedUser github.User 121 | err = json.Unmarshal([]byte(textContent.Text), &returnedUser) 122 | require.NoError(t, err) 123 | 124 | // Verify user details 125 | assert.Equal(t, *tc.expectedUser.Login, *returnedUser.Login) 126 | assert.Equal(t, *tc.expectedUser.Name, *returnedUser.Name) 127 | assert.Equal(t, *tc.expectedUser.Email, *returnedUser.Email) 128 | assert.Equal(t, *tc.expectedUser.Bio, *returnedUser.Bio) 129 | assert.Equal(t, *tc.expectedUser.HTMLURL, *returnedUser.HTMLURL) 130 | assert.Equal(t, *tc.expectedUser.Type, *returnedUser.Type) 131 | }) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /pkg/github/dynamic_tools.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "github.com/github/github-mcp-server/pkg/toolsets" 9 | "github.com/github/github-mcp-server/pkg/translations" 10 | "github.com/mark3labs/mcp-go/mcp" 11 | "github.com/mark3labs/mcp-go/server" 12 | ) 13 | 14 | func ToolsetEnum(toolsetGroup *toolsets.ToolsetGroup) mcp.PropertyOption { 15 | toolsetNames := make([]string, 0, len(toolsetGroup.Toolsets)) 16 | for name := range toolsetGroup.Toolsets { 17 | toolsetNames = append(toolsetNames, name) 18 | } 19 | return mcp.Enum(toolsetNames...) 20 | } 21 | 22 | func EnableToolset(s *server.MCPServer, toolsetGroup *toolsets.ToolsetGroup, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { 23 | return mcp.NewTool("enable_toolset", 24 | mcp.WithDescription(t("TOOL_ENABLE_TOOLSET_DESCRIPTION", "Enable one of the sets of tools the GitHub MCP server provides, use get_toolset_tools and list_available_toolsets first to see what this will enable")), 25 | mcp.WithToolAnnotation(mcp.ToolAnnotation{ 26 | Title: t("TOOL_ENABLE_TOOLSET_USER_TITLE", "Enable a toolset"), 27 | // Not modifying GitHub data so no need to show a warning 28 | ReadOnlyHint: toBoolPtr(true), 29 | }), 30 | mcp.WithString("toolset", 31 | mcp.Required(), 32 | mcp.Description("The name of the toolset to enable"), 33 | ToolsetEnum(toolsetGroup), 34 | ), 35 | ), 36 | func(_ context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 37 | // We need to convert the toolsets back to a map for JSON serialization 38 | toolsetName, err := requiredParam[string](request, "toolset") 39 | if err != nil { 40 | return mcp.NewToolResultError(err.Error()), nil 41 | } 42 | toolset := toolsetGroup.Toolsets[toolsetName] 43 | if toolset == nil { 44 | return mcp.NewToolResultError(fmt.Sprintf("Toolset %s not found", toolsetName)), nil 45 | } 46 | if toolset.Enabled { 47 | return mcp.NewToolResultText(fmt.Sprintf("Toolset %s is already enabled", toolsetName)), nil 48 | } 49 | 50 | toolset.Enabled = true 51 | 52 | // caution: this currently affects the global tools and notifies all clients: 53 | // 54 | // Send notification to all initialized sessions 55 | // s.sendNotificationToAllClients("notifications/tools/list_changed", nil) 56 | s.AddTools(toolset.GetActiveTools()...) 57 | 58 | return mcp.NewToolResultText(fmt.Sprintf("Toolset %s enabled", toolsetName)), nil 59 | } 60 | } 61 | 62 | func ListAvailableToolsets(toolsetGroup *toolsets.ToolsetGroup, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { 63 | return mcp.NewTool("list_available_toolsets", 64 | mcp.WithDescription(t("TOOL_LIST_AVAILABLE_TOOLSETS_DESCRIPTION", "List all available toolsets this GitHub MCP server can offer, providing the enabled status of each. Use this when a task could be achieved with a GitHub tool and the currently available tools aren't enough. Call get_toolset_tools with these toolset names to discover specific tools you can call")), 65 | mcp.WithToolAnnotation(mcp.ToolAnnotation{ 66 | Title: t("TOOL_LIST_AVAILABLE_TOOLSETS_USER_TITLE", "List available toolsets"), 67 | ReadOnlyHint: toBoolPtr(true), 68 | }), 69 | ), 70 | func(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) { 71 | // We need to convert the toolsetGroup back to a map for JSON serialization 72 | 73 | payload := []map[string]string{} 74 | 75 | for name, ts := range toolsetGroup.Toolsets { 76 | { 77 | t := map[string]string{ 78 | "name": name, 79 | "description": ts.Description, 80 | "can_enable": "true", 81 | "currently_enabled": fmt.Sprintf("%t", ts.Enabled), 82 | } 83 | payload = append(payload, t) 84 | } 85 | } 86 | 87 | r, err := json.Marshal(payload) 88 | if err != nil { 89 | return nil, fmt.Errorf("failed to marshal features: %w", err) 90 | } 91 | 92 | return mcp.NewToolResultText(string(r)), nil 93 | } 94 | } 95 | 96 | func GetToolsetsTools(toolsetGroup *toolsets.ToolsetGroup, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { 97 | return mcp.NewTool("get_toolset_tools", 98 | mcp.WithDescription(t("TOOL_GET_TOOLSET_TOOLS_DESCRIPTION", "Lists all the capabilities that are enabled with the specified toolset, use this to get clarity on whether enabling a toolset would help you to complete a task")), 99 | mcp.WithToolAnnotation(mcp.ToolAnnotation{ 100 | Title: t("TOOL_GET_TOOLSET_TOOLS_USER_TITLE", "List all tools in a toolset"), 101 | ReadOnlyHint: toBoolPtr(true), 102 | }), 103 | mcp.WithString("toolset", 104 | mcp.Required(), 105 | mcp.Description("The name of the toolset you want to get the tools for"), 106 | ToolsetEnum(toolsetGroup), 107 | ), 108 | ), 109 | func(_ context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 110 | // We need to convert the toolsetGroup back to a map for JSON serialization 111 | toolsetName, err := requiredParam[string](request, "toolset") 112 | if err != nil { 113 | return mcp.NewToolResultError(err.Error()), nil 114 | } 115 | toolset := toolsetGroup.Toolsets[toolsetName] 116 | if toolset == nil { 117 | return mcp.NewToolResultError(fmt.Sprintf("Toolset %s not found", toolsetName)), nil 118 | } 119 | payload := []map[string]string{} 120 | 121 | for _, st := range toolset.GetAvailableTools() { 122 | tool := map[string]string{ 123 | "name": st.Tool.Name, 124 | "description": st.Tool.Description, 125 | "can_enable": "true", 126 | "toolset": toolsetName, 127 | } 128 | payload = append(payload, tool) 129 | } 130 | 131 | r, err := json.Marshal(payload) 132 | if err != nil { 133 | return nil, fmt.Errorf("failed to marshal features: %w", err) 134 | } 135 | 136 | return mcp.NewToolResultText(string(r)), nil 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /pkg/github/resources.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "github.com/github/github-mcp-server/pkg/translations" 5 | "github.com/mark3labs/mcp-go/server" 6 | ) 7 | 8 | func RegisterResources(s *server.MCPServer, getClient GetClientFn, t translations.TranslationHelperFunc) { 9 | s.AddResourceTemplate(GetRepositoryResourceContent(getClient, t)) 10 | s.AddResourceTemplate(GetRepositoryResourceBranchContent(getClient, t)) 11 | s.AddResourceTemplate(GetRepositoryResourceCommitContent(getClient, t)) 12 | s.AddResourceTemplate(GetRepositoryResourceTagContent(getClient, t)) 13 | s.AddResourceTemplate(GetRepositoryResourcePrContent(getClient, t)) 14 | } 15 | -------------------------------------------------------------------------------- /pkg/github/search.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | 9 | "github.com/github/github-mcp-server/pkg/translations" 10 | "github.com/google/go-github/v69/github" 11 | "github.com/mark3labs/mcp-go/mcp" 12 | "github.com/mark3labs/mcp-go/server" 13 | ) 14 | 15 | // SearchRepositories creates a tool to search for GitHub repositories. 16 | func SearchRepositories(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { 17 | return mcp.NewTool("search_repositories", 18 | mcp.WithDescription(t("TOOL_SEARCH_REPOSITORIES_DESCRIPTION", "Search for GitHub repositories")), 19 | mcp.WithToolAnnotation(mcp.ToolAnnotation{ 20 | Title: t("TOOL_SEARCH_REPOSITORIES_USER_TITLE", "Search repositories"), 21 | ReadOnlyHint: toBoolPtr(true), 22 | }), 23 | mcp.WithString("query", 24 | mcp.Required(), 25 | mcp.Description("Search query"), 26 | ), 27 | WithPagination(), 28 | ), 29 | func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 30 | query, err := requiredParam[string](request, "query") 31 | if err != nil { 32 | return mcp.NewToolResultError(err.Error()), nil 33 | } 34 | pagination, err := OptionalPaginationParams(request) 35 | if err != nil { 36 | return mcp.NewToolResultError(err.Error()), nil 37 | } 38 | 39 | opts := &github.SearchOptions{ 40 | ListOptions: github.ListOptions{ 41 | Page: pagination.page, 42 | PerPage: pagination.perPage, 43 | }, 44 | } 45 | 46 | client, err := getClient(ctx) 47 | if err != nil { 48 | return nil, fmt.Errorf("failed to get GitHub client: %w", err) 49 | } 50 | result, resp, err := client.Search.Repositories(ctx, query, opts) 51 | if err != nil { 52 | return nil, fmt.Errorf("failed to search repositories: %w", err) 53 | } 54 | defer func() { _ = resp.Body.Close() }() 55 | 56 | if resp.StatusCode != 200 { 57 | body, err := io.ReadAll(resp.Body) 58 | if err != nil { 59 | return nil, fmt.Errorf("failed to read response body: %w", err) 60 | } 61 | return mcp.NewToolResultError(fmt.Sprintf("failed to search repositories: %s", string(body))), nil 62 | } 63 | 64 | r, err := json.Marshal(result) 65 | if err != nil { 66 | return nil, fmt.Errorf("failed to marshal response: %w", err) 67 | } 68 | 69 | return mcp.NewToolResultText(string(r)), nil 70 | } 71 | } 72 | 73 | // SearchCode creates a tool to search for code across GitHub repositories. 74 | func SearchCode(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { 75 | return mcp.NewTool("search_code", 76 | mcp.WithDescription(t("TOOL_SEARCH_CODE_DESCRIPTION", "Search for code across GitHub repositories")), 77 | mcp.WithToolAnnotation(mcp.ToolAnnotation{ 78 | Title: t("TOOL_SEARCH_CODE_USER_TITLE", "Search code"), 79 | ReadOnlyHint: toBoolPtr(true), 80 | }), 81 | mcp.WithString("q", 82 | mcp.Required(), 83 | mcp.Description("Search query using GitHub code search syntax"), 84 | ), 85 | mcp.WithString("sort", 86 | mcp.Description("Sort field ('indexed' only)"), 87 | ), 88 | mcp.WithString("order", 89 | mcp.Description("Sort order"), 90 | mcp.Enum("asc", "desc"), 91 | ), 92 | WithPagination(), 93 | ), 94 | func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 95 | query, err := requiredParam[string](request, "q") 96 | if err != nil { 97 | return mcp.NewToolResultError(err.Error()), nil 98 | } 99 | sort, err := OptionalParam[string](request, "sort") 100 | if err != nil { 101 | return mcp.NewToolResultError(err.Error()), nil 102 | } 103 | order, err := OptionalParam[string](request, "order") 104 | if err != nil { 105 | return mcp.NewToolResultError(err.Error()), nil 106 | } 107 | pagination, err := OptionalPaginationParams(request) 108 | if err != nil { 109 | return mcp.NewToolResultError(err.Error()), nil 110 | } 111 | 112 | opts := &github.SearchOptions{ 113 | Sort: sort, 114 | Order: order, 115 | ListOptions: github.ListOptions{ 116 | PerPage: pagination.perPage, 117 | Page: pagination.page, 118 | }, 119 | } 120 | 121 | client, err := getClient(ctx) 122 | if err != nil { 123 | return nil, fmt.Errorf("failed to get GitHub client: %w", err) 124 | } 125 | 126 | result, resp, err := client.Search.Code(ctx, query, opts) 127 | if err != nil { 128 | return nil, fmt.Errorf("failed to search code: %w", err) 129 | } 130 | defer func() { _ = resp.Body.Close() }() 131 | 132 | if resp.StatusCode != 200 { 133 | body, err := io.ReadAll(resp.Body) 134 | if err != nil { 135 | return nil, fmt.Errorf("failed to read response body: %w", err) 136 | } 137 | return mcp.NewToolResultError(fmt.Sprintf("failed to search code: %s", string(body))), nil 138 | } 139 | 140 | r, err := json.Marshal(result) 141 | if err != nil { 142 | return nil, fmt.Errorf("failed to marshal response: %w", err) 143 | } 144 | 145 | return mcp.NewToolResultText(string(r)), nil 146 | } 147 | } 148 | 149 | // SearchUsers creates a tool to search for GitHub users. 150 | func SearchUsers(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { 151 | return mcp.NewTool("search_users", 152 | mcp.WithDescription(t("TOOL_SEARCH_USERS_DESCRIPTION", "Search for GitHub users")), 153 | mcp.WithToolAnnotation(mcp.ToolAnnotation{ 154 | Title: t("TOOL_SEARCH_USERS_USER_TITLE", "Search users"), 155 | ReadOnlyHint: toBoolPtr(true), 156 | }), 157 | mcp.WithString("q", 158 | mcp.Required(), 159 | mcp.Description("Search query using GitHub users search syntax"), 160 | ), 161 | mcp.WithString("sort", 162 | mcp.Description("Sort field by category"), 163 | mcp.Enum("followers", "repositories", "joined"), 164 | ), 165 | mcp.WithString("order", 166 | mcp.Description("Sort order"), 167 | mcp.Enum("asc", "desc"), 168 | ), 169 | WithPagination(), 170 | ), 171 | func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 172 | query, err := requiredParam[string](request, "q") 173 | if err != nil { 174 | return mcp.NewToolResultError(err.Error()), nil 175 | } 176 | sort, err := OptionalParam[string](request, "sort") 177 | if err != nil { 178 | return mcp.NewToolResultError(err.Error()), nil 179 | } 180 | order, err := OptionalParam[string](request, "order") 181 | if err != nil { 182 | return mcp.NewToolResultError(err.Error()), nil 183 | } 184 | pagination, err := OptionalPaginationParams(request) 185 | if err != nil { 186 | return mcp.NewToolResultError(err.Error()), nil 187 | } 188 | 189 | opts := &github.SearchOptions{ 190 | Sort: sort, 191 | Order: order, 192 | ListOptions: github.ListOptions{ 193 | PerPage: pagination.perPage, 194 | Page: pagination.page, 195 | }, 196 | } 197 | 198 | client, err := getClient(ctx) 199 | if err != nil { 200 | return nil, fmt.Errorf("failed to get GitHub client: %w", err) 201 | } 202 | 203 | result, resp, err := client.Search.Users(ctx, query, opts) 204 | if err != nil { 205 | return nil, fmt.Errorf("failed to search users: %w", err) 206 | } 207 | defer func() { _ = resp.Body.Close() }() 208 | 209 | if resp.StatusCode != 200 { 210 | body, err := io.ReadAll(resp.Body) 211 | if err != nil { 212 | return nil, fmt.Errorf("failed to read response body: %w", err) 213 | } 214 | return mcp.NewToolResultError(fmt.Sprintf("failed to search users: %s", string(body))), nil 215 | } 216 | 217 | r, err := json.Marshal(result) 218 | if err != nil { 219 | return nil, fmt.Errorf("failed to marshal response: %w", err) 220 | } 221 | 222 | return mcp.NewToolResultText(string(r)), nil 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /pkg/github/secret_scanning.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | 10 | "github.com/github/github-mcp-server/pkg/translations" 11 | "github.com/google/go-github/v69/github" 12 | "github.com/mark3labs/mcp-go/mcp" 13 | "github.com/mark3labs/mcp-go/server" 14 | ) 15 | 16 | func GetSecretScanningAlert(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { 17 | return mcp.NewTool( 18 | "get_secret_scanning_alert", 19 | mcp.WithDescription(t("TOOL_GET_SECRET_SCANNING_ALERT_DESCRIPTION", "Get details of a specific secret scanning alert in a GitHub repository.")), 20 | mcp.WithToolAnnotation(mcp.ToolAnnotation{ 21 | Title: t("TOOL_GET_SECRET_SCANNING_ALERT_USER_TITLE", "Get secret scanning alert"), 22 | ReadOnlyHint: toBoolPtr(true), 23 | }), 24 | mcp.WithString("owner", 25 | mcp.Required(), 26 | mcp.Description("The owner of the repository."), 27 | ), 28 | mcp.WithString("repo", 29 | mcp.Required(), 30 | mcp.Description("The name of the repository."), 31 | ), 32 | mcp.WithNumber("alertNumber", 33 | mcp.Required(), 34 | mcp.Description("The number of the alert."), 35 | ), 36 | ), 37 | func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 38 | owner, err := requiredParam[string](request, "owner") 39 | if err != nil { 40 | return mcp.NewToolResultError(err.Error()), nil 41 | } 42 | repo, err := requiredParam[string](request, "repo") 43 | if err != nil { 44 | return mcp.NewToolResultError(err.Error()), nil 45 | } 46 | alertNumber, err := RequiredInt(request, "alertNumber") 47 | if err != nil { 48 | return mcp.NewToolResultError(err.Error()), nil 49 | } 50 | 51 | client, err := getClient(ctx) 52 | if err != nil { 53 | return nil, fmt.Errorf("failed to get GitHub client: %w", err) 54 | } 55 | 56 | alert, resp, err := client.SecretScanning.GetAlert(ctx, owner, repo, int64(alertNumber)) 57 | if err != nil { 58 | return nil, fmt.Errorf("failed to get alert: %w", err) 59 | } 60 | defer func() { _ = resp.Body.Close() }() 61 | 62 | if resp.StatusCode != http.StatusOK { 63 | body, err := io.ReadAll(resp.Body) 64 | if err != nil { 65 | return nil, fmt.Errorf("failed to read response body: %w", err) 66 | } 67 | return mcp.NewToolResultError(fmt.Sprintf("failed to get alert: %s", string(body))), nil 68 | } 69 | 70 | r, err := json.Marshal(alert) 71 | if err != nil { 72 | return nil, fmt.Errorf("failed to marshal alert: %w", err) 73 | } 74 | 75 | return mcp.NewToolResultText(string(r)), nil 76 | } 77 | } 78 | 79 | func ListSecretScanningAlerts(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { 80 | return mcp.NewTool( 81 | "list_secret_scanning_alerts", 82 | mcp.WithDescription(t("TOOL_LIST_SECRET_SCANNING_ALERTS_DESCRIPTION", "List secret scanning alerts in a GitHub repository.")), 83 | mcp.WithToolAnnotation(mcp.ToolAnnotation{ 84 | Title: t("TOOL_LIST_SECRET_SCANNING_ALERTS_USER_TITLE", "List secret scanning alerts"), 85 | ReadOnlyHint: toBoolPtr(true), 86 | }), 87 | mcp.WithString("owner", 88 | mcp.Required(), 89 | mcp.Description("The owner of the repository."), 90 | ), 91 | mcp.WithString("repo", 92 | mcp.Required(), 93 | mcp.Description("The name of the repository."), 94 | ), 95 | mcp.WithString("state", 96 | mcp.Description("Filter by state"), 97 | mcp.Enum("open", "resolved"), 98 | ), 99 | mcp.WithString("secret_type", 100 | mcp.Description("A comma-separated list of secret types to return. All default secret patterns are returned. To return generic patterns, pass the token name(s) in the parameter."), 101 | ), 102 | mcp.WithString("resolution", 103 | mcp.Description("Filter by resolution"), 104 | mcp.Enum("false_positive", "wont_fix", "revoked", "pattern_edited", "pattern_deleted", "used_in_tests"), 105 | ), 106 | ), 107 | func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 108 | owner, err := requiredParam[string](request, "owner") 109 | if err != nil { 110 | return mcp.NewToolResultError(err.Error()), nil 111 | } 112 | repo, err := requiredParam[string](request, "repo") 113 | if err != nil { 114 | return mcp.NewToolResultError(err.Error()), nil 115 | } 116 | state, err := OptionalParam[string](request, "state") 117 | if err != nil { 118 | return mcp.NewToolResultError(err.Error()), nil 119 | } 120 | secretType, err := OptionalParam[string](request, "secret_type") 121 | if err != nil { 122 | return mcp.NewToolResultError(err.Error()), nil 123 | } 124 | resolution, err := OptionalParam[string](request, "resolution") 125 | if err != nil { 126 | return mcp.NewToolResultError(err.Error()), nil 127 | } 128 | 129 | client, err := getClient(ctx) 130 | if err != nil { 131 | return nil, fmt.Errorf("failed to get GitHub client: %w", err) 132 | } 133 | alerts, resp, err := client.SecretScanning.ListAlertsForRepo(ctx, owner, repo, &github.SecretScanningAlertListOptions{State: state, SecretType: secretType, Resolution: resolution}) 134 | if err != nil { 135 | return nil, fmt.Errorf("failed to list alerts: %w", err) 136 | } 137 | defer func() { _ = resp.Body.Close() }() 138 | 139 | if resp.StatusCode != http.StatusOK { 140 | body, err := io.ReadAll(resp.Body) 141 | if err != nil { 142 | return nil, fmt.Errorf("failed to read response body: %w", err) 143 | } 144 | return mcp.NewToolResultError(fmt.Sprintf("failed to list alerts: %s", string(body))), nil 145 | } 146 | 147 | r, err := json.Marshal(alerts) 148 | if err != nil { 149 | return nil, fmt.Errorf("failed to marshal alerts: %w", err) 150 | } 151 | 152 | return mcp.NewToolResultText(string(r)), nil 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /pkg/github/server.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | 8 | "github.com/google/go-github/v69/github" 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | ) 12 | 13 | // NewServer creates a new GitHub MCP server with the specified GH client and logger. 14 | 15 | func NewServer(version string, opts ...server.ServerOption) *server.MCPServer { 16 | // Add default options 17 | defaultOpts := []server.ServerOption{ 18 | server.WithToolCapabilities(true), 19 | server.WithResourceCapabilities(true, true), 20 | server.WithLogging(), 21 | } 22 | opts = append(defaultOpts, opts...) 23 | 24 | // Create a new MCP server 25 | s := server.NewMCPServer( 26 | "github-mcp-server", 27 | version, 28 | opts..., 29 | ) 30 | return s 31 | } 32 | 33 | // OptionalParamOK is a helper function that can be used to fetch a requested parameter from the request. 34 | // It returns the value, a boolean indicating if the parameter was present, and an error if the type is wrong. 35 | func OptionalParamOK[T any](r mcp.CallToolRequest, p string) (value T, ok bool, err error) { 36 | // Check if the parameter is present in the request 37 | val, exists := r.GetArguments()[p] 38 | if !exists { 39 | // Not present, return zero value, false, no error 40 | return 41 | } 42 | 43 | // Check if the parameter is of the expected type 44 | value, ok = val.(T) 45 | if !ok { 46 | // Present but wrong type 47 | err = fmt.Errorf("parameter %s is not of type %T, is %T", p, value, val) 48 | ok = true // Set ok to true because the parameter *was* present, even if wrong type 49 | return 50 | } 51 | 52 | // Present and correct type 53 | ok = true 54 | return 55 | } 56 | 57 | // isAcceptedError checks if the error is an accepted error. 58 | func isAcceptedError(err error) bool { 59 | var acceptedError *github.AcceptedError 60 | return errors.As(err, &acceptedError) 61 | } 62 | 63 | // requiredParam is a helper function that can be used to fetch a requested parameter from the request. 64 | // It does the following checks: 65 | // 1. Checks if the parameter is present in the request. 66 | // 2. Checks if the parameter is of the expected type. 67 | // 3. Checks if the parameter is not empty, i.e: non-zero value 68 | func requiredParam[T comparable](r mcp.CallToolRequest, p string) (T, error) { 69 | var zero T 70 | 71 | // Check if the parameter is present in the request 72 | if _, ok := r.GetArguments()[p]; !ok { 73 | return zero, fmt.Errorf("missing required parameter: %s", p) 74 | } 75 | 76 | // Check if the parameter is of the expected type 77 | if _, ok := r.GetArguments()[p].(T); !ok { 78 | return zero, fmt.Errorf("parameter %s is not of type %T", p, zero) 79 | } 80 | 81 | if r.GetArguments()[p].(T) == zero { 82 | return zero, fmt.Errorf("missing required parameter: %s", p) 83 | 84 | } 85 | 86 | return r.GetArguments()[p].(T), nil 87 | } 88 | 89 | // RequiredInt is a helper function that can be used to fetch a requested parameter from the request. 90 | // It does the following checks: 91 | // 1. Checks if the parameter is present in the request. 92 | // 2. Checks if the parameter is of the expected type. 93 | // 3. Checks if the parameter is not empty, i.e: non-zero value 94 | func RequiredInt(r mcp.CallToolRequest, p string) (int, error) { 95 | v, err := requiredParam[float64](r, p) 96 | if err != nil { 97 | return 0, err 98 | } 99 | return int(v), nil 100 | } 101 | 102 | // OptionalParam is a helper function that can be used to fetch a requested parameter from the request. 103 | // It does the following checks: 104 | // 1. Checks if the parameter is present in the request, if not, it returns its zero-value 105 | // 2. If it is present, it checks if the parameter is of the expected type and returns it 106 | func OptionalParam[T any](r mcp.CallToolRequest, p string) (T, error) { 107 | var zero T 108 | 109 | // Check if the parameter is present in the request 110 | if _, ok := r.GetArguments()[p]; !ok { 111 | return zero, nil 112 | } 113 | 114 | // Check if the parameter is of the expected type 115 | if _, ok := r.GetArguments()[p].(T); !ok { 116 | return zero, fmt.Errorf("parameter %s is not of type %T, is %T", p, zero, r.GetArguments()[p]) 117 | } 118 | 119 | return r.GetArguments()[p].(T), nil 120 | } 121 | 122 | // OptionalIntParam is a helper function that can be used to fetch a requested parameter from the request. 123 | // It does the following checks: 124 | // 1. Checks if the parameter is present in the request, if not, it returns its zero-value 125 | // 2. If it is present, it checks if the parameter is of the expected type and returns it 126 | func OptionalIntParam(r mcp.CallToolRequest, p string) (int, error) { 127 | v, err := OptionalParam[float64](r, p) 128 | if err != nil { 129 | return 0, err 130 | } 131 | return int(v), nil 132 | } 133 | 134 | // OptionalIntParamWithDefault is a helper function that can be used to fetch a requested parameter from the request 135 | // similar to optionalIntParam, but it also takes a default value. 136 | func OptionalIntParamWithDefault(r mcp.CallToolRequest, p string, d int) (int, error) { 137 | v, err := OptionalIntParam(r, p) 138 | if err != nil { 139 | return 0, err 140 | } 141 | if v == 0 { 142 | return d, nil 143 | } 144 | return v, nil 145 | } 146 | 147 | // OptionalStringArrayParam is a helper function that can be used to fetch a requested parameter from the request. 148 | // It does the following checks: 149 | // 1. Checks if the parameter is present in the request, if not, it returns its zero-value 150 | // 2. If it is present, iterates the elements and checks each is a string 151 | func OptionalStringArrayParam(r mcp.CallToolRequest, p string) ([]string, error) { 152 | // Check if the parameter is present in the request 153 | if _, ok := r.GetArguments()[p]; !ok { 154 | return []string{}, nil 155 | } 156 | 157 | switch v := r.GetArguments()[p].(type) { 158 | case nil: 159 | return []string{}, nil 160 | case []string: 161 | return v, nil 162 | case []any: 163 | strSlice := make([]string, len(v)) 164 | for i, v := range v { 165 | s, ok := v.(string) 166 | if !ok { 167 | return []string{}, fmt.Errorf("parameter %s is not of type string, is %T", p, v) 168 | } 169 | strSlice[i] = s 170 | } 171 | return strSlice, nil 172 | default: 173 | return []string{}, fmt.Errorf("parameter %s could not be coerced to []string, is %T", p, r.GetArguments()[p]) 174 | } 175 | } 176 | 177 | // WithPagination returns a ToolOption that adds "page" and "perPage" parameters to the tool. 178 | // The "page" parameter is optional, min 1. The "perPage" parameter is optional, min 1, max 100. 179 | func WithPagination() mcp.ToolOption { 180 | return func(tool *mcp.Tool) { 181 | mcp.WithNumber("page", 182 | mcp.Description("Page number for pagination (min 1)"), 183 | mcp.Min(1), 184 | )(tool) 185 | 186 | mcp.WithNumber("perPage", 187 | mcp.Description("Results per page for pagination (min 1, max 100)"), 188 | mcp.Min(1), 189 | mcp.Max(100), 190 | )(tool) 191 | } 192 | } 193 | 194 | type PaginationParams struct { 195 | page int 196 | perPage int 197 | } 198 | 199 | // OptionalPaginationParams returns the "page" and "perPage" parameters from the request, 200 | // or their default values if not present, "page" default is 1, "perPage" default is 30. 201 | // In future, we may want to make the default values configurable, or even have this 202 | // function returned from `withPagination`, where the defaults are provided alongside 203 | // the min/max values. 204 | func OptionalPaginationParams(r mcp.CallToolRequest) (PaginationParams, error) { 205 | page, err := OptionalIntParamWithDefault(r, "page", 1) 206 | if err != nil { 207 | return PaginationParams{}, err 208 | } 209 | perPage, err := OptionalIntParamWithDefault(r, "perPage", 30) 210 | if err != nil { 211 | return PaginationParams{}, err 212 | } 213 | return PaginationParams{ 214 | page: page, 215 | perPage: perPage, 216 | }, nil 217 | } 218 | 219 | func MarshalledTextResult(v any) *mcp.CallToolResult { 220 | data, err := json.Marshal(v) 221 | if err != nil { 222 | return mcp.NewToolResultErrorFromErr("failed to marshal text result to json", err) 223 | } 224 | 225 | return mcp.NewToolResultText(string(data)) 226 | } 227 | -------------------------------------------------------------------------------- /pkg/github/tools.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/github/github-mcp-server/pkg/toolsets" 7 | "github.com/github/github-mcp-server/pkg/translations" 8 | "github.com/google/go-github/v69/github" 9 | "github.com/mark3labs/mcp-go/server" 10 | "github.com/shurcooL/githubv4" 11 | ) 12 | 13 | type GetClientFn func(context.Context) (*github.Client, error) 14 | type GetGQLClientFn func(context.Context) (*githubv4.Client, error) 15 | 16 | var DefaultTools = []string{"all"} 17 | 18 | func InitToolsets(passedToolsets []string, readOnly bool, getClient GetClientFn, getGQLClient GetGQLClientFn, t translations.TranslationHelperFunc) (*toolsets.ToolsetGroup, error) { 19 | // Create a new toolset group 20 | tsg := toolsets.NewToolsetGroup(readOnly) 21 | 22 | // Define all available features with their default state (disabled) 23 | // Create toolsets 24 | repos := toolsets.NewToolset("repos", "GitHub Repository related tools"). 25 | AddReadTools( 26 | toolsets.NewServerTool(SearchRepositories(getClient, t)), 27 | toolsets.NewServerTool(GetFileContents(getClient, t)), 28 | toolsets.NewServerTool(ListCommits(getClient, t)), 29 | toolsets.NewServerTool(SearchCode(getClient, t)), 30 | toolsets.NewServerTool(GetCommit(getClient, t)), 31 | toolsets.NewServerTool(ListBranches(getClient, t)), 32 | toolsets.NewServerTool(ListTags(getClient, t)), 33 | toolsets.NewServerTool(GetTag(getClient, t)), 34 | ). 35 | AddWriteTools( 36 | toolsets.NewServerTool(CreateOrUpdateFile(getClient, t)), 37 | toolsets.NewServerTool(CreateRepository(getClient, t)), 38 | toolsets.NewServerTool(ForkRepository(getClient, t)), 39 | toolsets.NewServerTool(CreateBranch(getClient, t)), 40 | toolsets.NewServerTool(PushFiles(getClient, t)), 41 | toolsets.NewServerTool(DeleteFile(getClient, t)), 42 | ) 43 | issues := toolsets.NewToolset("issues", "GitHub Issues related tools"). 44 | AddReadTools( 45 | toolsets.NewServerTool(GetIssue(getClient, t)), 46 | toolsets.NewServerTool(SearchIssues(getClient, t)), 47 | toolsets.NewServerTool(ListIssues(getClient, t)), 48 | toolsets.NewServerTool(GetIssueComments(getClient, t)), 49 | ). 50 | AddWriteTools( 51 | toolsets.NewServerTool(CreateIssue(getClient, t)), 52 | toolsets.NewServerTool(AddIssueComment(getClient, t)), 53 | toolsets.NewServerTool(UpdateIssue(getClient, t)), 54 | toolsets.NewServerTool(AssignCopilotToIssue(getGQLClient, t)), 55 | ) 56 | users := toolsets.NewToolset("users", "GitHub User related tools"). 57 | AddReadTools( 58 | toolsets.NewServerTool(SearchUsers(getClient, t)), 59 | ) 60 | pullRequests := toolsets.NewToolset("pull_requests", "GitHub Pull Request related tools"). 61 | AddReadTools( 62 | toolsets.NewServerTool(GetPullRequest(getClient, t)), 63 | toolsets.NewServerTool(ListPullRequests(getClient, t)), 64 | toolsets.NewServerTool(GetPullRequestFiles(getClient, t)), 65 | toolsets.NewServerTool(GetPullRequestStatus(getClient, t)), 66 | toolsets.NewServerTool(GetPullRequestComments(getClient, t)), 67 | toolsets.NewServerTool(GetPullRequestReviews(getClient, t)), 68 | toolsets.NewServerTool(GetPullRequestDiff(getClient, t)), 69 | ). 70 | AddWriteTools( 71 | toolsets.NewServerTool(MergePullRequest(getClient, t)), 72 | toolsets.NewServerTool(UpdatePullRequestBranch(getClient, t)), 73 | toolsets.NewServerTool(CreatePullRequest(getClient, t)), 74 | toolsets.NewServerTool(UpdatePullRequest(getClient, t)), 75 | toolsets.NewServerTool(RequestCopilotReview(getClient, t)), 76 | 77 | // Reviews 78 | toolsets.NewServerTool(CreateAndSubmitPullRequestReview(getGQLClient, t)), 79 | toolsets.NewServerTool(CreatePendingPullRequestReview(getGQLClient, t)), 80 | toolsets.NewServerTool(AddPullRequestReviewCommentToPendingReview(getGQLClient, t)), 81 | toolsets.NewServerTool(SubmitPendingPullRequestReview(getGQLClient, t)), 82 | toolsets.NewServerTool(DeletePendingPullRequestReview(getGQLClient, t)), 83 | ) 84 | codeSecurity := toolsets.NewToolset("code_security", "Code security related tools, such as GitHub Code Scanning"). 85 | AddReadTools( 86 | toolsets.NewServerTool(GetCodeScanningAlert(getClient, t)), 87 | toolsets.NewServerTool(ListCodeScanningAlerts(getClient, t)), 88 | ) 89 | secretProtection := toolsets.NewToolset("secret_protection", "Secret protection related tools, such as GitHub Secret Scanning"). 90 | AddReadTools( 91 | toolsets.NewServerTool(GetSecretScanningAlert(getClient, t)), 92 | toolsets.NewServerTool(ListSecretScanningAlerts(getClient, t)), 93 | ) 94 | 95 | notifications := toolsets.NewToolset("notifications", "GitHub Notifications related tools"). 96 | AddReadTools( 97 | toolsets.NewServerTool(ListNotifications(getClient, t)), 98 | toolsets.NewServerTool(GetNotificationDetails(getClient, t)), 99 | ). 100 | AddWriteTools( 101 | toolsets.NewServerTool(DismissNotification(getClient, t)), 102 | toolsets.NewServerTool(MarkAllNotificationsRead(getClient, t)), 103 | toolsets.NewServerTool(ManageNotificationSubscription(getClient, t)), 104 | toolsets.NewServerTool(ManageRepositoryNotificationSubscription(getClient, t)), 105 | ) 106 | 107 | // Keep experiments alive so the system doesn't error out when it's always enabled 108 | experiments := toolsets.NewToolset("experiments", "Experimental features that are not considered stable yet") 109 | 110 | // Add toolsets to the group 111 | tsg.AddToolset(repos) 112 | tsg.AddToolset(issues) 113 | tsg.AddToolset(users) 114 | tsg.AddToolset(pullRequests) 115 | tsg.AddToolset(codeSecurity) 116 | tsg.AddToolset(secretProtection) 117 | tsg.AddToolset(notifications) 118 | tsg.AddToolset(experiments) 119 | // Enable the requested features 120 | 121 | if err := tsg.EnableToolsets(passedToolsets); err != nil { 122 | return nil, err 123 | } 124 | 125 | return tsg, nil 126 | } 127 | 128 | func InitContextToolset(getClient GetClientFn, t translations.TranslationHelperFunc) *toolsets.Toolset { 129 | // Create a new context toolset 130 | contextTools := toolsets.NewToolset("context", "Tools that provide context about the current user and GitHub context you are operating in"). 131 | AddReadTools( 132 | toolsets.NewServerTool(GetMe(getClient, t)), 133 | ) 134 | contextTools.Enabled = true 135 | return contextTools 136 | } 137 | 138 | // InitDynamicToolset creates a dynamic toolset that can be used to enable other toolsets, and so requires the server and toolset group as arguments 139 | func InitDynamicToolset(s *server.MCPServer, tsg *toolsets.ToolsetGroup, t translations.TranslationHelperFunc) *toolsets.Toolset { 140 | // Create a new dynamic toolset 141 | // Need to add the dynamic toolset last so it can be used to enable other toolsets 142 | dynamicToolSelection := toolsets.NewToolset("dynamic", "Discover GitHub MCP tools that can help achieve tasks by enabling additional sets of tools, you can control the enablement of any toolset to access its tools when this toolset is enabled."). 143 | AddReadTools( 144 | toolsets.NewServerTool(ListAvailableToolsets(tsg, t)), 145 | toolsets.NewServerTool(GetToolsetsTools(tsg, t)), 146 | toolsets.NewServerTool(EnableToolset(s, tsg, t)), 147 | ) 148 | 149 | dynamicToolSelection.Enabled = true 150 | return dynamicToolSelection 151 | } 152 | 153 | func toBoolPtr(b bool) *bool { 154 | return &b 155 | } 156 | -------------------------------------------------------------------------------- /pkg/log/io.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | 6 | log "github.com/sirupsen/logrus" 7 | ) 8 | 9 | // IOLogger is a wrapper around io.Reader and io.Writer that can be used 10 | // to log the data being read and written from the underlying streams 11 | type IOLogger struct { 12 | reader io.Reader 13 | writer io.Writer 14 | logger *log.Logger 15 | } 16 | 17 | // NewIOLogger creates a new IOLogger instance 18 | func NewIOLogger(r io.Reader, w io.Writer, logger *log.Logger) *IOLogger { 19 | return &IOLogger{ 20 | reader: r, 21 | writer: w, 22 | logger: logger, 23 | } 24 | } 25 | 26 | // Read reads data from the underlying io.Reader and logs it. 27 | func (l *IOLogger) Read(p []byte) (n int, err error) { 28 | if l.reader == nil { 29 | return 0, io.EOF 30 | } 31 | n, err = l.reader.Read(p) 32 | if n > 0 { 33 | l.logger.Infof("[stdin]: received %d bytes: %s", n, string(p[:n])) 34 | } 35 | return n, err 36 | } 37 | 38 | // Write writes data to the underlying io.Writer and logs it. 39 | func (l *IOLogger) Write(p []byte) (n int, err error) { 40 | if l.writer == nil { 41 | return 0, io.ErrClosedPipe 42 | } 43 | l.logger.Infof("[stdout]: sending %d bytes: %s", len(p), string(p)) 44 | return l.writer.Write(p) 45 | } 46 | -------------------------------------------------------------------------------- /pkg/log/io_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | "testing" 7 | 8 | log "github.com/sirupsen/logrus" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestLoggedReadWriter(t *testing.T) { 13 | t.Run("Read method logs and passes data", func(t *testing.T) { 14 | // Setup 15 | inputData := "test input data" 16 | reader := strings.NewReader(inputData) 17 | 18 | // Create logger with buffer to capture output 19 | var logBuffer bytes.Buffer 20 | logger := log.New() 21 | logger.SetOutput(&logBuffer) 22 | logger.SetFormatter(&log.TextFormatter{ 23 | DisableTimestamp: true, 24 | }) 25 | 26 | lrw := NewIOLogger(reader, nil, logger) 27 | 28 | // Test Read 29 | buf := make([]byte, 100) 30 | n, err := lrw.Read(buf) 31 | 32 | // Assertions 33 | assert.NoError(t, err) 34 | assert.Equal(t, len(inputData), n) 35 | assert.Equal(t, inputData, string(buf[:n])) 36 | assert.Contains(t, logBuffer.String(), "[stdin]") 37 | assert.Contains(t, logBuffer.String(), inputData) 38 | }) 39 | 40 | t.Run("Write method logs and passes data", func(t *testing.T) { 41 | // Setup 42 | outputData := "test output data" 43 | var writeBuffer bytes.Buffer 44 | 45 | // Create logger with buffer to capture output 46 | var logBuffer bytes.Buffer 47 | logger := log.New() 48 | logger.SetOutput(&logBuffer) 49 | logger.SetFormatter(&log.TextFormatter{ 50 | DisableTimestamp: true, 51 | }) 52 | 53 | lrw := NewIOLogger(nil, &writeBuffer, logger) 54 | 55 | // Test Write 56 | n, err := lrw.Write([]byte(outputData)) 57 | 58 | // Assertions 59 | assert.NoError(t, err) 60 | assert.Equal(t, len(outputData), n) 61 | assert.Equal(t, outputData, writeBuffer.String()) 62 | assert.Contains(t, logBuffer.String(), "[stdout]") 63 | assert.Contains(t, logBuffer.String(), outputData) 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /pkg/toolsets/toolsets.go: -------------------------------------------------------------------------------- 1 | package toolsets 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mark3labs/mcp-go/mcp" 7 | "github.com/mark3labs/mcp-go/server" 8 | ) 9 | 10 | func NewServerTool(tool mcp.Tool, handler server.ToolHandlerFunc) server.ServerTool { 11 | return server.ServerTool{Tool: tool, Handler: handler} 12 | } 13 | 14 | type Toolset struct { 15 | Name string 16 | Description string 17 | Enabled bool 18 | readOnly bool 19 | writeTools []server.ServerTool 20 | readTools []server.ServerTool 21 | } 22 | 23 | func (t *Toolset) GetActiveTools() []server.ServerTool { 24 | if t.Enabled { 25 | if t.readOnly { 26 | return t.readTools 27 | } 28 | return append(t.readTools, t.writeTools...) 29 | } 30 | return nil 31 | } 32 | 33 | func (t *Toolset) GetAvailableTools() []server.ServerTool { 34 | if t.readOnly { 35 | return t.readTools 36 | } 37 | return append(t.readTools, t.writeTools...) 38 | } 39 | 40 | func (t *Toolset) RegisterTools(s *server.MCPServer) { 41 | if !t.Enabled { 42 | return 43 | } 44 | for _, tool := range t.readTools { 45 | s.AddTool(tool.Tool, tool.Handler) 46 | } 47 | if !t.readOnly { 48 | for _, tool := range t.writeTools { 49 | s.AddTool(tool.Tool, tool.Handler) 50 | } 51 | } 52 | } 53 | 54 | func (t *Toolset) SetReadOnly() { 55 | // Set the toolset to read-only 56 | t.readOnly = true 57 | } 58 | 59 | func (t *Toolset) AddWriteTools(tools ...server.ServerTool) *Toolset { 60 | // Silently ignore if the toolset is read-only to avoid any breach of that contract 61 | for _, tool := range tools { 62 | if *tool.Tool.Annotations.ReadOnlyHint { 63 | panic(fmt.Sprintf("tool (%s) is incorrectly annotated as read-only", tool.Tool.Name)) 64 | } 65 | } 66 | if !t.readOnly { 67 | t.writeTools = append(t.writeTools, tools...) 68 | } 69 | return t 70 | } 71 | 72 | func (t *Toolset) AddReadTools(tools ...server.ServerTool) *Toolset { 73 | for _, tool := range tools { 74 | if !*tool.Tool.Annotations.ReadOnlyHint { 75 | panic(fmt.Sprintf("tool (%s) must be annotated as read-only", tool.Tool.Name)) 76 | } 77 | } 78 | t.readTools = append(t.readTools, tools...) 79 | return t 80 | } 81 | 82 | type ToolsetGroup struct { 83 | Toolsets map[string]*Toolset 84 | everythingOn bool 85 | readOnly bool 86 | } 87 | 88 | func NewToolsetGroup(readOnly bool) *ToolsetGroup { 89 | return &ToolsetGroup{ 90 | Toolsets: make(map[string]*Toolset), 91 | everythingOn: false, 92 | readOnly: readOnly, 93 | } 94 | } 95 | 96 | func (tg *ToolsetGroup) AddToolset(ts *Toolset) { 97 | if tg.readOnly { 98 | ts.SetReadOnly() 99 | } 100 | tg.Toolsets[ts.Name] = ts 101 | } 102 | 103 | func NewToolset(name string, description string) *Toolset { 104 | return &Toolset{ 105 | Name: name, 106 | Description: description, 107 | Enabled: false, 108 | readOnly: false, 109 | } 110 | } 111 | 112 | func (tg *ToolsetGroup) IsEnabled(name string) bool { 113 | // If everythingOn is true, all features are enabled 114 | if tg.everythingOn { 115 | return true 116 | } 117 | 118 | feature, exists := tg.Toolsets[name] 119 | if !exists { 120 | return false 121 | } 122 | return feature.Enabled 123 | } 124 | 125 | func (tg *ToolsetGroup) EnableToolsets(names []string) error { 126 | // Special case for "all" 127 | for _, name := range names { 128 | if name == "all" { 129 | tg.everythingOn = true 130 | break 131 | } 132 | err := tg.EnableToolset(name) 133 | if err != nil { 134 | return err 135 | } 136 | } 137 | // Do this after to ensure all toolsets are enabled if "all" is present anywhere in list 138 | if tg.everythingOn { 139 | for name := range tg.Toolsets { 140 | err := tg.EnableToolset(name) 141 | if err != nil { 142 | return err 143 | } 144 | } 145 | return nil 146 | } 147 | return nil 148 | } 149 | 150 | func (tg *ToolsetGroup) EnableToolset(name string) error { 151 | toolset, exists := tg.Toolsets[name] 152 | if !exists { 153 | return fmt.Errorf("toolset %s does not exist", name) 154 | } 155 | toolset.Enabled = true 156 | tg.Toolsets[name] = toolset 157 | return nil 158 | } 159 | 160 | func (tg *ToolsetGroup) RegisterTools(s *server.MCPServer) { 161 | for _, toolset := range tg.Toolsets { 162 | toolset.RegisterTools(s) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /pkg/toolsets/toolsets_test.go: -------------------------------------------------------------------------------- 1 | package toolsets 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestNewToolsetGroupIsEmptyWithoutEverythingOn(t *testing.T) { 8 | tsg := NewToolsetGroup(false) 9 | if len(tsg.Toolsets) != 0 { 10 | t.Fatalf("Expected Toolsets map to be empty, got %d items", len(tsg.Toolsets)) 11 | } 12 | if tsg.everythingOn { 13 | t.Fatal("Expected everythingOn to be initialized as false") 14 | } 15 | } 16 | 17 | func TestAddToolset(t *testing.T) { 18 | tsg := NewToolsetGroup(false) 19 | 20 | // Test adding a toolset 21 | toolset := NewToolset("test-toolset", "A test toolset") 22 | toolset.Enabled = true 23 | tsg.AddToolset(toolset) 24 | 25 | // Verify toolset was added correctly 26 | if len(tsg.Toolsets) != 1 { 27 | t.Errorf("Expected 1 toolset, got %d", len(tsg.Toolsets)) 28 | } 29 | 30 | toolset, exists := tsg.Toolsets["test-toolset"] 31 | if !exists { 32 | t.Fatal("Feature was not added to the map") 33 | } 34 | 35 | if toolset.Name != "test-toolset" { 36 | t.Errorf("Expected toolset name to be 'test-toolset', got '%s'", toolset.Name) 37 | } 38 | 39 | if toolset.Description != "A test toolset" { 40 | t.Errorf("Expected toolset description to be 'A test toolset', got '%s'", toolset.Description) 41 | } 42 | 43 | if !toolset.Enabled { 44 | t.Error("Expected toolset to be enabled") 45 | } 46 | 47 | // Test adding another toolset 48 | anotherToolset := NewToolset("another-toolset", "Another test toolset") 49 | tsg.AddToolset(anotherToolset) 50 | 51 | if len(tsg.Toolsets) != 2 { 52 | t.Errorf("Expected 2 toolsets, got %d", len(tsg.Toolsets)) 53 | } 54 | 55 | // Test overriding existing toolset 56 | updatedToolset := NewToolset("test-toolset", "Updated description") 57 | tsg.AddToolset(updatedToolset) 58 | 59 | toolset = tsg.Toolsets["test-toolset"] 60 | if toolset.Description != "Updated description" { 61 | t.Errorf("Expected toolset description to be updated to 'Updated description', got '%s'", toolset.Description) 62 | } 63 | 64 | if toolset.Enabled { 65 | t.Error("Expected toolset to be disabled after update") 66 | } 67 | } 68 | 69 | func TestIsEnabled(t *testing.T) { 70 | tsg := NewToolsetGroup(false) 71 | 72 | // Test with non-existent toolset 73 | if tsg.IsEnabled("non-existent") { 74 | t.Error("Expected IsEnabled to return false for non-existent toolset") 75 | } 76 | 77 | // Test with disabled toolset 78 | disabledToolset := NewToolset("disabled-toolset", "A disabled toolset") 79 | tsg.AddToolset(disabledToolset) 80 | if tsg.IsEnabled("disabled-toolset") { 81 | t.Error("Expected IsEnabled to return false for disabled toolset") 82 | } 83 | 84 | // Test with enabled toolset 85 | enabledToolset := NewToolset("enabled-toolset", "An enabled toolset") 86 | enabledToolset.Enabled = true 87 | tsg.AddToolset(enabledToolset) 88 | if !tsg.IsEnabled("enabled-toolset") { 89 | t.Error("Expected IsEnabled to return true for enabled toolset") 90 | } 91 | } 92 | 93 | func TestEnableFeature(t *testing.T) { 94 | tsg := NewToolsetGroup(false) 95 | 96 | // Test enabling non-existent toolset 97 | err := tsg.EnableToolset("non-existent") 98 | if err == nil { 99 | t.Error("Expected error when enabling non-existent toolset") 100 | } 101 | 102 | // Test enabling toolset 103 | testToolset := NewToolset("test-toolset", "A test toolset") 104 | tsg.AddToolset(testToolset) 105 | 106 | if tsg.IsEnabled("test-toolset") { 107 | t.Error("Expected toolset to be disabled initially") 108 | } 109 | 110 | err = tsg.EnableToolset("test-toolset") 111 | if err != nil { 112 | t.Errorf("Expected no error when enabling toolset, got: %v", err) 113 | } 114 | 115 | if !tsg.IsEnabled("test-toolset") { 116 | t.Error("Expected toolset to be enabled after EnableFeature call") 117 | } 118 | 119 | // Test enabling already enabled toolset 120 | err = tsg.EnableToolset("test-toolset") 121 | if err != nil { 122 | t.Errorf("Expected no error when enabling already enabled toolset, got: %v", err) 123 | } 124 | } 125 | 126 | func TestEnableToolsets(t *testing.T) { 127 | tsg := NewToolsetGroup(false) 128 | 129 | // Prepare toolsets 130 | toolset1 := NewToolset("toolset1", "Feature 1") 131 | toolset2 := NewToolset("toolset2", "Feature 2") 132 | tsg.AddToolset(toolset1) 133 | tsg.AddToolset(toolset2) 134 | 135 | // Test enabling multiple toolsets 136 | err := tsg.EnableToolsets([]string{"toolset1", "toolset2"}) 137 | if err != nil { 138 | t.Errorf("Expected no error when enabling toolsets, got: %v", err) 139 | } 140 | 141 | if !tsg.IsEnabled("toolset1") { 142 | t.Error("Expected toolset1 to be enabled") 143 | } 144 | 145 | if !tsg.IsEnabled("toolset2") { 146 | t.Error("Expected toolset2 to be enabled") 147 | } 148 | 149 | // Test with non-existent toolset in the list 150 | err = tsg.EnableToolsets([]string{"toolset1", "non-existent"}) 151 | if err == nil { 152 | t.Error("Expected error when enabling list with non-existent toolset") 153 | } 154 | 155 | // Test with empty list 156 | err = tsg.EnableToolsets([]string{}) 157 | if err != nil { 158 | t.Errorf("Expected no error with empty toolset list, got: %v", err) 159 | } 160 | 161 | // Test enabling everything through EnableToolsets 162 | tsg = NewToolsetGroup(false) 163 | err = tsg.EnableToolsets([]string{"all"}) 164 | if err != nil { 165 | t.Errorf("Expected no error when enabling 'all', got: %v", err) 166 | } 167 | 168 | if !tsg.everythingOn { 169 | t.Error("Expected everythingOn to be true after enabling 'all' via EnableToolsets") 170 | } 171 | } 172 | 173 | func TestEnableEverything(t *testing.T) { 174 | tsg := NewToolsetGroup(false) 175 | 176 | // Add a disabled toolset 177 | testToolset := NewToolset("test-toolset", "A test toolset") 178 | tsg.AddToolset(testToolset) 179 | 180 | // Verify it's disabled 181 | if tsg.IsEnabled("test-toolset") { 182 | t.Error("Expected toolset to be disabled initially") 183 | } 184 | 185 | // Enable "all" 186 | err := tsg.EnableToolsets([]string{"all"}) 187 | if err != nil { 188 | t.Errorf("Expected no error when enabling 'eall', got: %v", err) 189 | } 190 | 191 | // Verify everythingOn was set 192 | if !tsg.everythingOn { 193 | t.Error("Expected everythingOn to be true after enabling 'eall'") 194 | } 195 | 196 | // Verify the previously disabled toolset is now enabled 197 | if !tsg.IsEnabled("test-toolset") { 198 | t.Error("Expected toolset to be enabled when everythingOn is true") 199 | } 200 | 201 | // Verify a non-existent toolset is also enabled 202 | if !tsg.IsEnabled("non-existent") { 203 | t.Error("Expected non-existent toolset to be enabled when everythingOn is true") 204 | } 205 | } 206 | 207 | func TestIsEnabledWithEverythingOn(t *testing.T) { 208 | tsg := NewToolsetGroup(false) 209 | 210 | // Enable "everything" 211 | err := tsg.EnableToolsets([]string{"all"}) 212 | if err != nil { 213 | t.Errorf("Expected no error when enabling 'all', got: %v", err) 214 | } 215 | 216 | // Test that any toolset name returns true with IsEnabled 217 | if !tsg.IsEnabled("some-toolset") { 218 | t.Error("Expected IsEnabled to return true for any toolset when everythingOn is true") 219 | } 220 | 221 | if !tsg.IsEnabled("another-toolset") { 222 | t.Error("Expected IsEnabled to return true for any toolset when everythingOn is true") 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /pkg/translations/translations.go: -------------------------------------------------------------------------------- 1 | package translations 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "os" 8 | "strings" 9 | 10 | "github.com/spf13/viper" 11 | ) 12 | 13 | type TranslationHelperFunc func(key string, defaultValue string) string 14 | 15 | func NullTranslationHelper(_ string, defaultValue string) string { 16 | return defaultValue 17 | } 18 | 19 | func TranslationHelper() (TranslationHelperFunc, func()) { 20 | var translationKeyMap = map[string]string{} 21 | v := viper.New() 22 | 23 | // Load from JSON file 24 | v.SetConfigName("github-mcp-server-config") 25 | v.SetConfigType("json") 26 | v.AddConfigPath(".") 27 | 28 | if err := v.ReadInConfig(); err != nil { 29 | // ignore error if file not found as it is not required 30 | if _, ok := err.(viper.ConfigFileNotFoundError); !ok { 31 | log.Printf("Could not read JSON config: %v", err) 32 | } 33 | } 34 | 35 | // create a function that takes both a key, and a default value and returns either the default value or an override value 36 | return func(key string, defaultValue string) string { 37 | key = strings.ToUpper(key) 38 | if value, exists := translationKeyMap[key]; exists { 39 | return value 40 | } 41 | // check if the env var exists 42 | if value, exists := os.LookupEnv("GITHUB_MCP_" + key); exists { 43 | // TODO I could not get Viper to play ball reading the env var 44 | translationKeyMap[key] = value 45 | return value 46 | } 47 | 48 | v.SetDefault(key, defaultValue) 49 | translationKeyMap[key] = v.GetString(key) 50 | return translationKeyMap[key] 51 | }, func() { 52 | // dump the translationKeyMap to a json file 53 | if err := DumpTranslationKeyMap(translationKeyMap); err != nil { 54 | log.Fatalf("Could not dump translation key map: %v", err) 55 | } 56 | } 57 | } 58 | 59 | // DumpTranslationKeyMap writes the translation map to a json file called github-mcp-server-config.json 60 | func DumpTranslationKeyMap(translationKeyMap map[string]string) error { 61 | file, err := os.Create("github-mcp-server-config.json") 62 | if err != nil { 63 | return fmt.Errorf("error creating file: %v", err) 64 | } 65 | defer func() { _ = file.Close() }() 66 | 67 | // marshal the map to json 68 | jsonData, err := json.MarshalIndent(translationKeyMap, "", " ") 69 | if err != nil { 70 | return fmt.Errorf("error marshaling map to JSON: %v", err) 71 | } 72 | 73 | // write the json data to the file 74 | if _, err := file.Write(jsonData); err != nil { 75 | return fmt.Errorf("error writing to file: %v", err) 76 | } 77 | 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /script/get-me: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo '{"jsonrpc":"2.0","id":3,"params":{"name":"get_me"},"method":"tools/call"}' | go run cmd/github-mcp-server/main.go stdio | jq . 4 | -------------------------------------------------------------------------------- /script/licenses: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | go install github.com/google/go-licenses@latest 4 | 5 | rm -rf third-party 6 | mkdir -p third-party 7 | export TEMPDIR="$(mktemp -d)" 8 | 9 | trap "rm -fr ${TEMPDIR}" EXIT 10 | 11 | for goos in linux darwin windows ; do 12 | # Note: we ignore warnings because we want the command to succeed, however the output should be checked 13 | # for any new warnings, and potentially we may need to add license information. 14 | # 15 | # Normally these warnings are packages containing non go code, which may or may not require explicit attribution, 16 | # depending on the license. 17 | GOOS="${goos}" go-licenses save ./... --save_path="${TEMPDIR}/${goos}" --force || echo "Ignore warnings" 18 | GOOS="${goos}" go-licenses report ./... --template .github/licenses.tmpl > third-party-licenses.${goos}.md || echo "Ignore warnings" 19 | cp -fR "${TEMPDIR}/${goos}"/* third-party/ 20 | done 21 | 22 | -------------------------------------------------------------------------------- /script/licenses-check: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | go install github.com/google/go-licenses@latest 4 | 5 | for goos in linux darwin windows ; do 6 | # Note: we ignore warnings because we want the command to succeed, however the output should be checked 7 | # for any new warnings, and potentially we may need to add license information. 8 | # 9 | # Normally these warnings are packages containing non go code, which may or may not require explicit attribution, 10 | # depending on the license. 11 | GOOS="${goos}" go-licenses report ./... --template .github/licenses.tmpl > third-party-licenses.${goos}.copy.md || echo "Ignore warnings" 12 | if ! diff -s third-party-licenses.${goos}.copy.md third-party-licenses.${goos}.md; then 13 | echo "License check failed.\n\nPlease update the license file by running \`.script/licenses\` and committing the output." 14 | rm -f third-party-licenses.${goos}.copy.md 15 | exit 1 16 | fi 17 | rm -f third-party-licenses.${goos}.copy.md 18 | done 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /script/prettyprint-log: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Script to pretty print the output of the github-mcp-server 4 | # log. 5 | # 6 | # It uses colored output when running on a terminal. 7 | 8 | # show script help 9 | show_help() { 10 | cat <&2 55 | exit 1 56 | fi 57 | input="$1" 58 | else 59 | input="/dev/stdin" 60 | fi 61 | 62 | # check if we are in a terminal for showing colors 63 | if test -t 1; then 64 | is_terminal="1" 65 | else 66 | is_terminal="0" 67 | fi 68 | 69 | # Processs each log line, print whether is stdin or stdout, using different 70 | # colors if we output to a terminal, and pretty print json data using jq 71 | sed -nE 's/^.*\[(stdin|stdout)\]:.* ([0-9]+) bytes: (.*)\\n"$/\1 \2 \3/p' $input | 72 | while read -r io bytes json; do 73 | # Unescape the JSON string safely 74 | unescaped=$(echo "$json" | awk '{ print "echo -e \"" $0 "\" | jq ." }' | bash) 75 | echo "$(color $io)($bytes bytes):$(reset)" 76 | echo "$unescaped" | jq . 77 | echo 78 | done 79 | -------------------------------------------------------------------------------- /third-party-licenses.darwin.md: -------------------------------------------------------------------------------- 1 | # GitHub MCP Server dependencies 2 | 3 | The following open source dependencies are used to build the [github/github-mcp-server][] GitHub Model Context Protocol Server. 4 | 5 | ## Go Packages 6 | 7 | Some packages may only be included on certain architectures or operating systems. 8 | 9 | 10 | - [github.com/fsnotify/fsnotify](https://pkg.go.dev/github.com/fsnotify/fsnotify) ([BSD-3-Clause](https://github.com/fsnotify/fsnotify/blob/v1.8.0/LICENSE)) 11 | - [github.com/github/github-mcp-server](https://pkg.go.dev/github.com/github/github-mcp-server) ([MIT](https://github.com/github/github-mcp-server/blob/HEAD/LICENSE)) 12 | - [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.19.5/LICENSE)) 13 | - [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.21.1/LICENSE)) 14 | - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.2.1/LICENSE)) 15 | - [github.com/google/go-github/v69/github](https://pkg.go.dev/github.com/google/go-github/v69/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v69.2.0/LICENSE)) 16 | - [github.com/google/go-querystring/query](https://pkg.go.dev/github.com/google/go-querystring/query) ([BSD-3-Clause](https://github.com/google/go-querystring/blob/v1.1.0/LICENSE)) 17 | - [github.com/google/uuid](https://pkg.go.dev/github.com/google/uuid) ([BSD-3-Clause](https://github.com/google/uuid/blob/v1.6.0/LICENSE)) 18 | - [github.com/josephburnett/jd/v2](https://pkg.go.dev/github.com/josephburnett/jd/v2) ([MIT](https://github.com/josephburnett/jd/blob/v1.9.2/LICENSE)) 19 | - [github.com/josharian/intern](https://pkg.go.dev/github.com/josharian/intern) ([MIT](https://github.com/josharian/intern/blob/v1.0.0/license.md)) 20 | - [github.com/mailru/easyjson](https://pkg.go.dev/github.com/mailru/easyjson) ([MIT](https://github.com/mailru/easyjson/blob/v0.7.7/LICENSE)) 21 | - [github.com/mark3labs/mcp-go](https://pkg.go.dev/github.com/mark3labs/mcp-go) ([MIT](https://github.com/mark3labs/mcp-go/blob/v0.30.0/LICENSE)) 22 | - [github.com/pelletier/go-toml/v2](https://pkg.go.dev/github.com/pelletier/go-toml/v2) ([MIT](https://github.com/pelletier/go-toml/blob/v2.2.3/LICENSE)) 23 | - [github.com/sagikazarmark/locafero](https://pkg.go.dev/github.com/sagikazarmark/locafero) ([MIT](https://github.com/sagikazarmark/locafero/blob/v0.9.0/LICENSE)) 24 | - [github.com/shurcooL/githubv4](https://pkg.go.dev/github.com/shurcooL/githubv4) ([MIT](https://github.com/shurcooL/githubv4/blob/48295856cce7/LICENSE)) 25 | - [github.com/shurcooL/graphql](https://pkg.go.dev/github.com/shurcooL/graphql) ([MIT](https://github.com/shurcooL/graphql/blob/ed46e5a46466/LICENSE)) 26 | - [github.com/sirupsen/logrus](https://pkg.go.dev/github.com/sirupsen/logrus) ([MIT](https://github.com/sirupsen/logrus/blob/v1.9.3/LICENSE)) 27 | - [github.com/sourcegraph/conc](https://pkg.go.dev/github.com/sourcegraph/conc) ([MIT](https://github.com/sourcegraph/conc/blob/v0.3.0/LICENSE)) 28 | - [github.com/spf13/afero](https://pkg.go.dev/github.com/spf13/afero) ([Apache-2.0](https://github.com/spf13/afero/blob/v1.14.0/LICENSE.txt)) 29 | - [github.com/spf13/cast](https://pkg.go.dev/github.com/spf13/cast) ([MIT](https://github.com/spf13/cast/blob/v1.7.1/LICENSE)) 30 | - [github.com/spf13/cobra](https://pkg.go.dev/github.com/spf13/cobra) ([Apache-2.0](https://github.com/spf13/cobra/blob/v1.9.1/LICENSE.txt)) 31 | - [github.com/spf13/pflag](https://pkg.go.dev/github.com/spf13/pflag) ([BSD-3-Clause](https://github.com/spf13/pflag/blob/v1.0.6/LICENSE)) 32 | - [github.com/spf13/viper](https://pkg.go.dev/github.com/spf13/viper) ([MIT](https://github.com/spf13/viper/blob/v1.20.1/LICENSE)) 33 | - [github.com/subosito/gotenv](https://pkg.go.dev/github.com/subosito/gotenv) ([MIT](https://github.com/subosito/gotenv/blob/v1.6.0/LICENSE)) 34 | - [github.com/yosida95/uritemplate/v3](https://pkg.go.dev/github.com/yosida95/uritemplate/v3) ([BSD-3-Clause](https://github.com/yosida95/uritemplate/blob/v3.0.2/LICENSE)) 35 | - [github.com/yudai/golcs](https://pkg.go.dev/github.com/yudai/golcs) ([MIT](https://github.com/yudai/golcs/blob/ecda9a501e82/LICENSE)) 36 | - [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/8a7402ab:LICENSE)) 37 | - [golang.org/x/sys/unix](https://pkg.go.dev/golang.org/x/sys/unix) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.31.0:LICENSE)) 38 | - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.23.0:LICENSE)) 39 | - [gopkg.in/yaml.v2](https://pkg.go.dev/gopkg.in/yaml.v2) ([Apache-2.0](https://github.com/go-yaml/yaml/blob/v2.4.0/LICENSE)) 40 | - [gopkg.in/yaml.v3](https://pkg.go.dev/gopkg.in/yaml.v3) ([MIT](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE)) 41 | 42 | [github/github-mcp-server]: https://github.com/github/github-mcp-server 43 | -------------------------------------------------------------------------------- /third-party-licenses.linux.md: -------------------------------------------------------------------------------- 1 | # GitHub MCP Server dependencies 2 | 3 | The following open source dependencies are used to build the [github/github-mcp-server][] GitHub Model Context Protocol Server. 4 | 5 | ## Go Packages 6 | 7 | Some packages may only be included on certain architectures or operating systems. 8 | 9 | 10 | - [github.com/fsnotify/fsnotify](https://pkg.go.dev/github.com/fsnotify/fsnotify) ([BSD-3-Clause](https://github.com/fsnotify/fsnotify/blob/v1.8.0/LICENSE)) 11 | - [github.com/github/github-mcp-server](https://pkg.go.dev/github.com/github/github-mcp-server) ([MIT](https://github.com/github/github-mcp-server/blob/HEAD/LICENSE)) 12 | - [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.19.5/LICENSE)) 13 | - [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.21.1/LICENSE)) 14 | - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.2.1/LICENSE)) 15 | - [github.com/google/go-github/v69/github](https://pkg.go.dev/github.com/google/go-github/v69/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v69.2.0/LICENSE)) 16 | - [github.com/google/go-querystring/query](https://pkg.go.dev/github.com/google/go-querystring/query) ([BSD-3-Clause](https://github.com/google/go-querystring/blob/v1.1.0/LICENSE)) 17 | - [github.com/google/uuid](https://pkg.go.dev/github.com/google/uuid) ([BSD-3-Clause](https://github.com/google/uuid/blob/v1.6.0/LICENSE)) 18 | - [github.com/josephburnett/jd/v2](https://pkg.go.dev/github.com/josephburnett/jd/v2) ([MIT](https://github.com/josephburnett/jd/blob/v1.9.2/LICENSE)) 19 | - [github.com/josharian/intern](https://pkg.go.dev/github.com/josharian/intern) ([MIT](https://github.com/josharian/intern/blob/v1.0.0/license.md)) 20 | - [github.com/mailru/easyjson](https://pkg.go.dev/github.com/mailru/easyjson) ([MIT](https://github.com/mailru/easyjson/blob/v0.7.7/LICENSE)) 21 | - [github.com/mark3labs/mcp-go](https://pkg.go.dev/github.com/mark3labs/mcp-go) ([MIT](https://github.com/mark3labs/mcp-go/blob/v0.30.0/LICENSE)) 22 | - [github.com/pelletier/go-toml/v2](https://pkg.go.dev/github.com/pelletier/go-toml/v2) ([MIT](https://github.com/pelletier/go-toml/blob/v2.2.3/LICENSE)) 23 | - [github.com/sagikazarmark/locafero](https://pkg.go.dev/github.com/sagikazarmark/locafero) ([MIT](https://github.com/sagikazarmark/locafero/blob/v0.9.0/LICENSE)) 24 | - [github.com/shurcooL/githubv4](https://pkg.go.dev/github.com/shurcooL/githubv4) ([MIT](https://github.com/shurcooL/githubv4/blob/48295856cce7/LICENSE)) 25 | - [github.com/shurcooL/graphql](https://pkg.go.dev/github.com/shurcooL/graphql) ([MIT](https://github.com/shurcooL/graphql/blob/ed46e5a46466/LICENSE)) 26 | - [github.com/sirupsen/logrus](https://pkg.go.dev/github.com/sirupsen/logrus) ([MIT](https://github.com/sirupsen/logrus/blob/v1.9.3/LICENSE)) 27 | - [github.com/sourcegraph/conc](https://pkg.go.dev/github.com/sourcegraph/conc) ([MIT](https://github.com/sourcegraph/conc/blob/v0.3.0/LICENSE)) 28 | - [github.com/spf13/afero](https://pkg.go.dev/github.com/spf13/afero) ([Apache-2.0](https://github.com/spf13/afero/blob/v1.14.0/LICENSE.txt)) 29 | - [github.com/spf13/cast](https://pkg.go.dev/github.com/spf13/cast) ([MIT](https://github.com/spf13/cast/blob/v1.7.1/LICENSE)) 30 | - [github.com/spf13/cobra](https://pkg.go.dev/github.com/spf13/cobra) ([Apache-2.0](https://github.com/spf13/cobra/blob/v1.9.1/LICENSE.txt)) 31 | - [github.com/spf13/pflag](https://pkg.go.dev/github.com/spf13/pflag) ([BSD-3-Clause](https://github.com/spf13/pflag/blob/v1.0.6/LICENSE)) 32 | - [github.com/spf13/viper](https://pkg.go.dev/github.com/spf13/viper) ([MIT](https://github.com/spf13/viper/blob/v1.20.1/LICENSE)) 33 | - [github.com/subosito/gotenv](https://pkg.go.dev/github.com/subosito/gotenv) ([MIT](https://github.com/subosito/gotenv/blob/v1.6.0/LICENSE)) 34 | - [github.com/yosida95/uritemplate/v3](https://pkg.go.dev/github.com/yosida95/uritemplate/v3) ([BSD-3-Clause](https://github.com/yosida95/uritemplate/blob/v3.0.2/LICENSE)) 35 | - [github.com/yudai/golcs](https://pkg.go.dev/github.com/yudai/golcs) ([MIT](https://github.com/yudai/golcs/blob/ecda9a501e82/LICENSE)) 36 | - [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/8a7402ab:LICENSE)) 37 | - [golang.org/x/sys/unix](https://pkg.go.dev/golang.org/x/sys/unix) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.31.0:LICENSE)) 38 | - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.23.0:LICENSE)) 39 | - [gopkg.in/yaml.v2](https://pkg.go.dev/gopkg.in/yaml.v2) ([Apache-2.0](https://github.com/go-yaml/yaml/blob/v2.4.0/LICENSE)) 40 | - [gopkg.in/yaml.v3](https://pkg.go.dev/gopkg.in/yaml.v3) ([MIT](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE)) 41 | 42 | [github/github-mcp-server]: https://github.com/github/github-mcp-server 43 | -------------------------------------------------------------------------------- /third-party-licenses.windows.md: -------------------------------------------------------------------------------- 1 | # GitHub MCP Server dependencies 2 | 3 | The following open source dependencies are used to build the [github/github-mcp-server][] GitHub Model Context Protocol Server. 4 | 5 | ## Go Packages 6 | 7 | Some packages may only be included on certain architectures or operating systems. 8 | 9 | 10 | - [github.com/fsnotify/fsnotify](https://pkg.go.dev/github.com/fsnotify/fsnotify) ([BSD-3-Clause](https://github.com/fsnotify/fsnotify/blob/v1.8.0/LICENSE)) 11 | - [github.com/github/github-mcp-server](https://pkg.go.dev/github.com/github/github-mcp-server) ([MIT](https://github.com/github/github-mcp-server/blob/HEAD/LICENSE)) 12 | - [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.19.5/LICENSE)) 13 | - [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.21.1/LICENSE)) 14 | - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.2.1/LICENSE)) 15 | - [github.com/google/go-github/v69/github](https://pkg.go.dev/github.com/google/go-github/v69/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v69.2.0/LICENSE)) 16 | - [github.com/google/go-querystring/query](https://pkg.go.dev/github.com/google/go-querystring/query) ([BSD-3-Clause](https://github.com/google/go-querystring/blob/v1.1.0/LICENSE)) 17 | - [github.com/google/uuid](https://pkg.go.dev/github.com/google/uuid) ([BSD-3-Clause](https://github.com/google/uuid/blob/v1.6.0/LICENSE)) 18 | - [github.com/inconshreveable/mousetrap](https://pkg.go.dev/github.com/inconshreveable/mousetrap) ([Apache-2.0](https://github.com/inconshreveable/mousetrap/blob/v1.1.0/LICENSE)) 19 | - [github.com/josephburnett/jd/v2](https://pkg.go.dev/github.com/josephburnett/jd/v2) ([MIT](https://github.com/josephburnett/jd/blob/v1.9.2/LICENSE)) 20 | - [github.com/josharian/intern](https://pkg.go.dev/github.com/josharian/intern) ([MIT](https://github.com/josharian/intern/blob/v1.0.0/license.md)) 21 | - [github.com/mailru/easyjson](https://pkg.go.dev/github.com/mailru/easyjson) ([MIT](https://github.com/mailru/easyjson/blob/v0.7.7/LICENSE)) 22 | - [github.com/mark3labs/mcp-go](https://pkg.go.dev/github.com/mark3labs/mcp-go) ([MIT](https://github.com/mark3labs/mcp-go/blob/v0.30.0/LICENSE)) 23 | - [github.com/pelletier/go-toml/v2](https://pkg.go.dev/github.com/pelletier/go-toml/v2) ([MIT](https://github.com/pelletier/go-toml/blob/v2.2.3/LICENSE)) 24 | - [github.com/sagikazarmark/locafero](https://pkg.go.dev/github.com/sagikazarmark/locafero) ([MIT](https://github.com/sagikazarmark/locafero/blob/v0.9.0/LICENSE)) 25 | - [github.com/shurcooL/githubv4](https://pkg.go.dev/github.com/shurcooL/githubv4) ([MIT](https://github.com/shurcooL/githubv4/blob/48295856cce7/LICENSE)) 26 | - [github.com/shurcooL/graphql](https://pkg.go.dev/github.com/shurcooL/graphql) ([MIT](https://github.com/shurcooL/graphql/blob/ed46e5a46466/LICENSE)) 27 | - [github.com/sirupsen/logrus](https://pkg.go.dev/github.com/sirupsen/logrus) ([MIT](https://github.com/sirupsen/logrus/blob/v1.9.3/LICENSE)) 28 | - [github.com/sourcegraph/conc](https://pkg.go.dev/github.com/sourcegraph/conc) ([MIT](https://github.com/sourcegraph/conc/blob/v0.3.0/LICENSE)) 29 | - [github.com/spf13/afero](https://pkg.go.dev/github.com/spf13/afero) ([Apache-2.0](https://github.com/spf13/afero/blob/v1.14.0/LICENSE.txt)) 30 | - [github.com/spf13/cast](https://pkg.go.dev/github.com/spf13/cast) ([MIT](https://github.com/spf13/cast/blob/v1.7.1/LICENSE)) 31 | - [github.com/spf13/cobra](https://pkg.go.dev/github.com/spf13/cobra) ([Apache-2.0](https://github.com/spf13/cobra/blob/v1.9.1/LICENSE.txt)) 32 | - [github.com/spf13/pflag](https://pkg.go.dev/github.com/spf13/pflag) ([BSD-3-Clause](https://github.com/spf13/pflag/blob/v1.0.6/LICENSE)) 33 | - [github.com/spf13/viper](https://pkg.go.dev/github.com/spf13/viper) ([MIT](https://github.com/spf13/viper/blob/v1.20.1/LICENSE)) 34 | - [github.com/subosito/gotenv](https://pkg.go.dev/github.com/subosito/gotenv) ([MIT](https://github.com/subosito/gotenv/blob/v1.6.0/LICENSE)) 35 | - [github.com/yosida95/uritemplate/v3](https://pkg.go.dev/github.com/yosida95/uritemplate/v3) ([BSD-3-Clause](https://github.com/yosida95/uritemplate/blob/v3.0.2/LICENSE)) 36 | - [github.com/yudai/golcs](https://pkg.go.dev/github.com/yudai/golcs) ([MIT](https://github.com/yudai/golcs/blob/ecda9a501e82/LICENSE)) 37 | - [golang.org/x/exp](https://pkg.go.dev/golang.org/x/exp) ([BSD-3-Clause](https://cs.opensource.google/go/x/exp/+/8a7402ab:LICENSE)) 38 | - [golang.org/x/sys/windows](https://pkg.go.dev/golang.org/x/sys/windows) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.31.0:LICENSE)) 39 | - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.23.0:LICENSE)) 40 | - [gopkg.in/yaml.v2](https://pkg.go.dev/gopkg.in/yaml.v2) ([Apache-2.0](https://github.com/go-yaml/yaml/blob/v2.4.0/LICENSE)) 41 | - [gopkg.in/yaml.v3](https://pkg.go.dev/gopkg.in/yaml.v3) ([MIT](https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE)) 42 | 43 | [github/github-mcp-server]: https://github.com/github/github-mcp-server 44 | -------------------------------------------------------------------------------- /third-party/github.com/fsnotify/fsnotify/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2012 The Go Authors. All rights reserved. 2 | Copyright © fsnotify Authors. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, this 10 | list of conditions and the following disclaimer in the documentation and/or 11 | other materials provided with the distribution. 12 | * Neither the name of Google Inc. nor the names of its contributors may be used 13 | to endorse or promote products derived from this software without specific 14 | prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /third-party/github.com/github/github-mcp-server/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 GitHub 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /third-party/github.com/go-viper/mapstructure/v2/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Mitchell Hashimoto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /third-party/github.com/google/go-github/v69/github/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 The go-github AUTHORS. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /third-party/github.com/google/go-querystring/query/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Google. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /third-party/github.com/google/uuid/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009,2014 Google Inc. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /third-party/github.com/josephburnett/jd/v2/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Joseph Burnett 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /third-party/github.com/josharian/intern/license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Josh Bleecher Snyder 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /third-party/github.com/mailru/easyjson/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Mail.Ru Group 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /third-party/github.com/mark3labs/mcp-go/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Anthropic, PBC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /third-party/github.com/pelletier/go-toml/v2/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | go-toml v2 4 | Copyright (c) 2021 - 2023 Thomas Pelletier 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /third-party/github.com/sagikazarmark/locafero/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Márk Sági-Kazár 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /third-party/github.com/shurcooL/githubv4/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Dmitri Shuralyov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /third-party/github.com/shurcooL/graphql/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Dmitri Shuralyov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /third-party/github.com/sirupsen/logrus/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Simon Eskildsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /third-party/github.com/sourcegraph/conc/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Sourcegraph 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /third-party/github.com/spf13/cast/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Steve Francia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /third-party/github.com/spf13/pflag/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Alex Ogier. All rights reserved. 2 | Copyright (c) 2012 The Go Authors. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Google Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /third-party/github.com/spf13/viper/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Steve Francia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /third-party/github.com/subosito/gotenv/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Alif Rachmawadi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /third-party/github.com/yosida95/uritemplate/v3/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2016, Kohei YOSHIDA . All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the copyright holder nor the names of its 12 | contributors may be used to endorse or promote products derived from 13 | this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 19 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 21 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /third-party/github.com/yudai/golcs/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Iwasaki Yudai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /third-party/golang.org/x/exp/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2009 The Go Authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /third-party/golang.org/x/sys/unix/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2009 The Go Authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /third-party/golang.org/x/sys/windows/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2009 The Go Authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /third-party/golang.org/x/text/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2009 The Go Authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /third-party/gopkg.in/yaml.v2/NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2011-2016 Canonical Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /third-party/gopkg.in/yaml.v3/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | This project is covered by two different licenses: MIT and Apache. 3 | 4 | #### MIT License #### 5 | 6 | The following files were ported to Go from C files of libyaml, and thus 7 | are still covered by their original MIT license, with the additional 8 | copyright staring in 2011 when the project was ported over: 9 | 10 | apic.go emitterc.go parserc.go readerc.go scannerc.go 11 | writerc.go yamlh.go yamlprivateh.go 12 | 13 | Copyright (c) 2006-2010 Kirill Simonov 14 | Copyright (c) 2006-2011 Kirill Simonov 15 | 16 | Permission is hereby granted, free of charge, to any person obtaining a copy of 17 | this software and associated documentation files (the "Software"), to deal in 18 | the Software without restriction, including without limitation the rights to 19 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 20 | of the Software, and to permit persons to whom the Software is furnished to do 21 | so, subject to the following conditions: 22 | 23 | The above copyright notice and this permission notice shall be included in all 24 | copies or substantial portions of the Software. 25 | 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | SOFTWARE. 33 | 34 | ### Apache License ### 35 | 36 | All the remaining project files are covered by the Apache license: 37 | 38 | Copyright (c) 2011-2019 Canonical Ltd 39 | 40 | Licensed under the Apache License, Version 2.0 (the "License"); 41 | you may not use this file except in compliance with the License. 42 | You may obtain a copy of the License at 43 | 44 | http://www.apache.org/licenses/LICENSE-2.0 45 | 46 | Unless required by applicable law or agreed to in writing, software 47 | distributed under the License is distributed on an "AS IS" BASIS, 48 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 49 | See the License for the specific language governing permissions and 50 | limitations under the License. 51 | -------------------------------------------------------------------------------- /third-party/gopkg.in/yaml.v3/NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2011-2016 Canonical Ltd. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | --------------------------------------------------------------------------------