├── .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 │ ├── docs-check.yml │ ├── go.yml │ ├── goreleaser.yml │ ├── license-check.yml │ └── lint.yml ├── .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 │ ├── generate_docs.go │ └── main.go └── mcpcurl │ ├── README.md │ └── main.go ├── docs ├── error-handling.md ├── host-integration.md ├── remote-server.md └── 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 ├── errors │ ├── error.go │ └── error_test.go ├── github │ ├── __toolsnaps__ │ │ ├── add_issue_comment.snap │ │ ├── add_pull_request_review_comment_to_pending_review.snap │ │ ├── assign_copilot_to_issue.snap │ │ ├── create_and_submit_pull_request_review.snap │ │ ├── create_branch.snap │ │ ├── create_issue.snap │ │ ├── create_or_update_file.snap │ │ ├── create_pending_pull_request_review.snap │ │ ├── create_pull_request.snap │ │ ├── create_repository.snap │ │ ├── delete_file.snap │ │ ├── delete_pending_pull_request_review.snap │ │ ├── dismiss_notification.snap │ │ ├── fork_repository.snap │ │ ├── get_code_scanning_alert.snap │ │ ├── get_commit.snap │ │ ├── get_dependabot_alert.snap │ │ ├── get_file_contents.snap │ │ ├── get_issue.snap │ │ ├── get_issue_comments.snap │ │ ├── get_me.snap │ │ ├── get_notification_details.snap │ │ ├── get_pull_request.snap │ │ ├── get_pull_request_comments.snap │ │ ├── get_pull_request_diff.snap │ │ ├── get_pull_request_files.snap │ │ ├── get_pull_request_reviews.snap │ │ ├── get_pull_request_status.snap │ │ ├── get_tag.snap │ │ ├── list_branches.snap │ │ ├── list_code_scanning_alerts.snap │ │ ├── list_commits.snap │ │ ├── list_dependabot_alerts.snap │ │ ├── list_issues.snap │ │ ├── list_notifications.snap │ │ ├── list_pull_requests.snap │ │ ├── list_tags.snap │ │ ├── manage_notification_subscription.snap │ │ ├── manage_repository_notification_subscription.snap │ │ ├── mark_all_notifications_read.snap │ │ ├── merge_pull_request.snap │ │ ├── push_files.snap │ │ ├── request_copilot_review.snap │ │ ├── search_code.snap │ │ ├── search_issues.snap │ │ ├── search_pull_requests.snap │ │ ├── search_repositories.snap │ │ ├── search_users.snap │ │ ├── submit_pending_pull_request_review.snap │ │ ├── update_issue.snap │ │ ├── update_pull_request.snap │ │ └── update_pull_request_branch.snap │ ├── actions.go │ ├── actions_test.go │ ├── code_scanning.go │ ├── code_scanning_test.go │ ├── context_tools.go │ ├── context_tools_test.go │ ├── dependabot.go │ ├── dependabot_test.go │ ├── discussions.go │ ├── discussions_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 │ ├── search.go │ ├── search_test.go │ ├── search_utils.go │ ├── secret_scanning.go │ ├── secret_scanning_test.go │ ├── server.go │ ├── server_test.go │ └── tools.go ├── log │ ├── io.go │ └── io_test.go ├── raw │ ├── raw.go │ ├── raw_mock.go │ └── raw_test.go ├── toolsets │ ├── toolsets.go │ └── toolsets_test.go └── translations │ └── translations.go ├── script ├── generate-docs ├── get-discussions ├── get-me ├── licenses ├── licenses-check ├── lint ├── prettyprint-log ├── tag-release └── test ├── 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 │ │ ├── v71 │ │ │ └── github │ │ │ │ └── LICENSE │ │ └── v73 │ │ │ └── github │ │ │ └── LICENSE │ ├── go-querystring │ │ └── query │ │ │ └── LICENSE │ └── uuid │ │ └── LICENSE ├── gorilla │ └── mux │ │ └── LICENSE ├── inconshreveable │ └── mousetrap │ │ └── LICENSE ├── josephburnett │ └── jd │ │ └── v2 │ │ └── LICENSE ├── josharian │ └── intern │ │ └── license.md ├── mailru │ └── easyjson │ │ └── LICENSE ├── mark3labs │ └── mcp-go │ │ └── LICENSE ├── migueleliasweb │ └── go-github-mock │ │ └── src │ │ └── mock │ │ └── 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 │ └── time │ └── rate │ └── 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 | ### Example prompts or workflows (for tools/toolsets only) 19 | 20 | If it's a new tool or improvement, share 3–5 example prompts or workflows it would enable. Just enough detail to show the value. Clear, valuable use cases are more likely to get approved. 21 | 22 | ### Additional context 23 | 24 | Add any other context like screenshots or mockups are helpful, if applicable. 25 | -------------------------------------------------------------------------------- /.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 | <!-- 2 | Thank you for contributing to GitHub MCP Server! 3 | Please reference an existing issue: `Closes #NUMBER` 4 | 5 | Screenshots or videos of changed behavior is incredibly helpful and always appreciated. 6 | Consider addressing the following: 7 | - Tradeoffs: List tradeoffs you made to take on or pay down tech debt. 8 | - Alternatives: Describe alternative approaches you considered and why you discarded them. 9 | --> 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", "next"] 13 | # Publish semver tags as releases. 14 | tags: ["v*.*.*"] 15 | pull_request: 16 | branches: ["main", "next"] 17 | 18 | env: 19 | # Use docker.io for Docker Hub if empty 20 | REGISTRY: ghcr.io 21 | # github.repository as <account>/<repo> 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/docs-check.yml: -------------------------------------------------------------------------------- 1 | name: Documentation Check 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | docs-check: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 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: Build docs generator 25 | run: go build -o github-mcp-server ./cmd/github-mcp-server 26 | 27 | - name: Generate documentation 28 | run: ./github-mcp-server generate-docs 29 | 30 | - name: Check for documentation changes 31 | run: | 32 | if ! git diff --exit-code README.md; then 33 | echo "❌ Documentation is out of date!" 34 | echo "" 35 | echo "The generated documentation differs from what's committed." 36 | echo "Please run the following command to update the documentation:" 37 | echo "" 38 | echo " go run ./cmd/github-mcp-server generate-docs" 39 | echo "" 40 | echo "Then commit the changes." 41 | echo "" 42 | echo "Changes detected:" 43 | git diff README.md 44 | exit 1 45 | else 46 | echo "✅ Documentation is up to date!" 47 | fi 48 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test Go Project 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: script/test 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.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | golangci: 13 | name: lint 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: actions/setup-go@v5 18 | with: 19 | go-version: stable 20 | - name: golangci-lint 21 | uses: golangci/golangci-lint-action@v8 22 | with: 23 | version: v2.1 24 | -------------------------------------------------------------------------------- /.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 | bin/ 15 | 16 | # macOS 17 | .DS_Store -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | concurrency: 4 4 | tests: true 5 | linters: 6 | enable: 7 | - bodyclose 8 | - gocritic 9 | - gosec 10 | - makezero 11 | - misspell 12 | - nakedret 13 | - revive 14 | exclusions: 15 | generated: lax 16 | presets: 17 | - comments 18 | - common-false-positives 19 | - legacy 20 | - std-error-handling 21 | paths: 22 | - third_party$ 23 | - builtin$ 24 | - examples$ 25 | settings: 26 | staticcheck: 27 | checks: 28 | - "all" 29 | - -QF1008 30 | - -ST1000 31 | formatters: 32 | exclusions: 33 | generated: lax 34 | paths: 35 | - third_party$ 36 | - builtin$ 37 | - examples$ 38 | -------------------------------------------------------------------------------- /.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 | } -------------------------------------------------------------------------------- /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 | 2. [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 | 2. Make sure the tests pass on your machine: `go test -v ./...` 24 | 3. Make sure linter passes on your machine: `golangci-lint run` 25 | 4. Create a new branch: `git checkout -b my-branch-name` 26 | 5. Add your changes and tests, and make sure the Action workflows still pass 27 | - Run linter: `script/lint` 28 | - Update snapshots and run tests: `UPDATE_TOOLSNAPS=true go test ./...` 29 | - Update readme documentation: `script/generate-docs` 30 | 6. Push to your fork and [submit a pull request][pr] targeting the `main` branch 31 | 7. Pat yourself on the back and wait for your pull request to be reviewed and merged. 32 | 33 | Here are a few things you can do that will increase the likelihood of your pull request being accepted: 34 | 35 | - Follow the [style guide][style]. 36 | - Write tests. 37 | - 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. 38 | - Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). 39 | 40 | ## Resources 41 | 42 | - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) 43 | - [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) 44 | - [GitHub Help](https://help.github.com) 45 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.24.4-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 | # Set the entrypoint to the server binary 26 | ENTRYPOINT ["/server/github-mcp-server"] 27 | # Default arguments for ENTRYPOINT 28 | CMD ["stdio"] 29 | -------------------------------------------------------------------------------- /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 | "strings" 8 | 9 | "github.com/github/github-mcp-server/internal/ghmcp" 10 | "github.com/github/github-mcp-server/pkg/github" 11 | "github.com/spf13/cobra" 12 | "github.com/spf13/pflag" 13 | "github.com/spf13/viper" 14 | ) 15 | 16 | // These variables are set by the build process using ldflags. 17 | var version = "version" 18 | var commit = "commit" 19 | var date = "date" 20 | 21 | var ( 22 | rootCmd = &cobra.Command{ 23 | Use: "server", 24 | Short: "GitHub MCP Server", 25 | Long: `A GitHub MCP server that handles various tools and resources.`, 26 | Version: fmt.Sprintf("Version: %s\nCommit: %s\nBuild Date: %s", version, commit, date), 27 | } 28 | 29 | stdioCmd = &cobra.Command{ 30 | Use: "stdio", 31 | Short: "Start stdio server", 32 | Long: `Start a server that communicates via standard input/output streams using JSON-RPC messages.`, 33 | RunE: func(_ *cobra.Command, _ []string) error { 34 | token := viper.GetString("personal_access_token") 35 | if token == "" { 36 | return errors.New("GITHUB_PERSONAL_ACCESS_TOKEN not set") 37 | } 38 | 39 | // If you're wondering why we're not using viper.GetStringSlice("toolsets"), 40 | // it's because viper doesn't handle comma-separated values correctly for env 41 | // vars when using GetStringSlice. 42 | // https://github.com/spf13/viper/issues/380 43 | var enabledToolsets []string 44 | if err := viper.UnmarshalKey("toolsets", &enabledToolsets); err != nil { 45 | return fmt.Errorf("failed to unmarshal toolsets: %w", err) 46 | } 47 | 48 | stdioServerConfig := ghmcp.StdioServerConfig{ 49 | Version: version, 50 | Host: viper.GetString("host"), 51 | Token: token, 52 | EnabledToolsets: enabledToolsets, 53 | DynamicToolsets: viper.GetBool("dynamic_toolsets"), 54 | ReadOnly: viper.GetBool("read-only"), 55 | ExportTranslations: viper.GetBool("export-translations"), 56 | EnableCommandLogging: viper.GetBool("enable-command-logging"), 57 | LogFilePath: viper.GetString("log-file"), 58 | } 59 | return ghmcp.RunStdioServer(stdioServerConfig) 60 | }, 61 | } 62 | ) 63 | 64 | func init() { 65 | cobra.OnInitialize(initConfig) 66 | rootCmd.SetGlobalNormalizationFunc(wordSepNormalizeFunc) 67 | 68 | rootCmd.SetVersionTemplate("{{.Short}}\n{{.Version}}\n") 69 | 70 | // Add global flags that will be shared by all commands 71 | rootCmd.PersistentFlags().StringSlice("toolsets", github.DefaultTools, "An optional comma separated list of groups of tools to allow, defaults to enabling all") 72 | rootCmd.PersistentFlags().Bool("dynamic-toolsets", false, "Enable dynamic toolsets") 73 | rootCmd.PersistentFlags().Bool("read-only", false, "Restrict the server to read-only operations") 74 | rootCmd.PersistentFlags().String("log-file", "", "Path to log file") 75 | rootCmd.PersistentFlags().Bool("enable-command-logging", false, "When enabled, the server will log all command requests and responses to the log file") 76 | rootCmd.PersistentFlags().Bool("export-translations", false, "Save translations to a JSON file") 77 | rootCmd.PersistentFlags().String("gh-host", "", "Specify the GitHub hostname (for GitHub Enterprise etc.)") 78 | 79 | // Bind flag to viper 80 | _ = viper.BindPFlag("toolsets", rootCmd.PersistentFlags().Lookup("toolsets")) 81 | _ = viper.BindPFlag("dynamic_toolsets", rootCmd.PersistentFlags().Lookup("dynamic-toolsets")) 82 | _ = viper.BindPFlag("read-only", rootCmd.PersistentFlags().Lookup("read-only")) 83 | _ = viper.BindPFlag("log-file", rootCmd.PersistentFlags().Lookup("log-file")) 84 | _ = viper.BindPFlag("enable-command-logging", rootCmd.PersistentFlags().Lookup("enable-command-logging")) 85 | _ = viper.BindPFlag("export-translations", rootCmd.PersistentFlags().Lookup("export-translations")) 86 | _ = viper.BindPFlag("host", rootCmd.PersistentFlags().Lookup("gh-host")) 87 | 88 | // Add subcommands 89 | rootCmd.AddCommand(stdioCmd) 90 | } 91 | 92 | func initConfig() { 93 | // Initialize Viper configuration 94 | viper.SetEnvPrefix("github") 95 | viper.AutomaticEnv() 96 | 97 | } 98 | 99 | func main() { 100 | if err := rootCmd.Execute(); err != nil { 101 | fmt.Fprintf(os.Stderr, "%v\n", err) 102 | os.Exit(1) 103 | } 104 | } 105 | 106 | func wordSepNormalizeFunc(_ *pflag.FlagSet, name string) pflag.NormalizedName { 107 | from := []string{"_"} 108 | to := "-" 109 | for _, sep := range from { 110 | name = strings.ReplaceAll(name, sep, to) 111 | } 112 | return pflag.NormalizedName(name) 113 | } 114 | -------------------------------------------------------------------------------- /docs/error-handling.md: -------------------------------------------------------------------------------- 1 | # Error Handling 2 | 3 | This document describes the error handling patterns used in the GitHub MCP Server, specifically how we handle GitHub API errors and avoid direct use of mcp-go error types. 4 | 5 | ## Overview 6 | 7 | The GitHub MCP Server implements a custom error handling approach that serves two primary purposes: 8 | 9 | 1. **Tool Response Generation**: Return appropriate MCP tool error responses to clients 10 | 2. **Middleware Inspection**: Store detailed error information in the request context for middleware analysis 11 | 12 | This dual approach enables better observability and debugging capabilities, particularly for remote server deployments where understanding the nature of failures (rate limiting, authentication, 404s, 500s, etc.) is crucial for validation and monitoring. 13 | 14 | ## Error Types 15 | 16 | ### GitHubAPIError 17 | 18 | Used for REST API errors from the GitHub API: 19 | 20 | ```go 21 | type GitHubAPIError struct { 22 | Message string `json:"message"` 23 | Response *github.Response `json:"-"` 24 | Err error `json:"-"` 25 | } 26 | ``` 27 | 28 | ### GitHubGraphQLError 29 | 30 | Used for GraphQL API errors from the GitHub API: 31 | 32 | ```go 33 | type GitHubGraphQLError struct { 34 | Message string `json:"message"` 35 | Err error `json:"-"` 36 | } 37 | ``` 38 | 39 | ## Usage Patterns 40 | 41 | ### For GitHub REST API Errors 42 | 43 | Instead of directly returning `mcp.NewToolResultError()`, use: 44 | 45 | ```go 46 | return ghErrors.NewGitHubAPIErrorResponse(ctx, message, response, err), nil 47 | ``` 48 | 49 | This function: 50 | - Creates a `GitHubAPIError` with the provided message, response, and error 51 | - Stores the error in the context for middleware inspection 52 | - Returns an appropriate MCP tool error response 53 | 54 | ### For GitHub GraphQL API Errors 55 | 56 | ```go 57 | return ghErrors.NewGitHubGraphQLErrorResponse(ctx, message, err), nil 58 | ``` 59 | 60 | ### Context Management 61 | 62 | The error handling system uses context to store errors for later inspection: 63 | 64 | ```go 65 | // Initialize context with error tracking 66 | ctx = errors.ContextWithGitHubErrors(ctx) 67 | 68 | // Retrieve errors for inspection (typically in middleware) 69 | apiErrors, err := errors.GetGitHubAPIErrors(ctx) 70 | graphqlErrors, err := errors.GetGitHubGraphQLErrors(ctx) 71 | ``` 72 | 73 | ## Design Principles 74 | 75 | ### User-Actionable vs. Developer Errors 76 | 77 | - **User-actionable errors** (authentication failures, rate limits, 404s) should be returned as failed tool calls using the error response functions 78 | - **Developer errors** (JSON marshaling failures, internal logic errors) should be returned as actual Go errors that bubble up through the MCP framework 79 | 80 | ### Context Limitations 81 | 82 | This approach was designed to work around current limitations in mcp-go where context is not propagated through each step of request processing. By storing errors in context values, middleware can inspect them without requiring context propagation. 83 | 84 | ### Graceful Error Handling 85 | 86 | Error storage operations in context are designed to fail gracefully - if context storage fails, the tool will still return an appropriate error response to the client. 87 | 88 | ## Benefits 89 | 90 | 1. **Observability**: Middleware can inspect the specific types of GitHub API errors occurring 91 | 2. **Debugging**: Detailed error information is preserved without exposing potentially sensitive data in logs 92 | 3. **Validation**: Remote servers can use error types and HTTP status codes to validate that changes don't break functionality 93 | 4. **Privacy**: Error inspection can be done programmatically using `errors.Is` checks without logging PII 94 | 95 | ## Example Implementation 96 | 97 | ```go 98 | func GetIssue(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { 99 | return mcp.NewTool("get_issue", /* ... */), 100 | func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { 101 | owner, err := RequiredParam[string](request, "owner") 102 | if err != nil { 103 | return mcp.NewToolResultError(err.Error()), nil 104 | } 105 | 106 | client, err := getClient(ctx) 107 | if err != nil { 108 | return nil, fmt.Errorf("failed to get GitHub client: %w", err) 109 | } 110 | 111 | issue, resp, err := client.Issues.Get(ctx, owner, repo, issueNumber) 112 | if err != nil { 113 | return ghErrors.NewGitHubAPIErrorResponse(ctx, 114 | "failed to get issue", 115 | resp, 116 | err, 117 | ), nil 118 | } 119 | 120 | return MarshalledTextResult(issue), nil 121 | } 122 | } 123 | ``` 124 | 125 | This approach ensures that both the client receives an appropriate error response and any middleware can inspect the underlying GitHub API error for monitoring and debugging purposes. 126 | -------------------------------------------------------------------------------- /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=<YOUR 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/v73 v73.0.0 7 | github.com/josephburnett/jd v1.9.2 8 | github.com/mark3labs/mcp-go v0.32.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.3.0 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 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/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/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\nrun with `UPDATE_TOOLSNAPS=true` if this is expected", 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 | // Ensure that UPDATE_TOOLSNAPS is not set for this test, which it might be if someone is running 47 | // UPDATE_TOOLSNAPS=true go test ./... 48 | t.Setenv("UPDATE_TOOLSNAPS", "false") 49 | 50 | // Given we are running in CI 51 | t.Setenv("GITHUB_ACTIONS", "true") 52 | tool := dummyTool{"foo", 42} 53 | 54 | // When we test the snapshot 55 | err := Test("dummy", tool) 56 | 57 | // Then it should error about missing snapshot in CI 58 | require.Error(t, err) 59 | assert.Contains(t, err.Error(), "tool snapshot does not exist", "expected error about missing snapshot in CI") 60 | } 61 | 62 | func TestSnapshotExistsMatch(t *testing.T) { 63 | withIsolatedWorkingDir(t) 64 | 65 | // Given a matching snapshot file exists 66 | tool := dummyTool{"foo", 42} 67 | b, _ := json.MarshalIndent(tool, "", " ") 68 | require.NoError(t, os.MkdirAll("__toolsnaps__", 0700)) 69 | require.NoError(t, os.WriteFile(filepath.Join("__toolsnaps__", "dummy.snap"), b, 0600)) 70 | 71 | // When we test the snapshot 72 | err := Test("dummy", tool) 73 | 74 | // Then it should succeed (no error) 75 | require.NoError(t, err) 76 | } 77 | 78 | func TestSnapshotExistsDiff(t *testing.T) { 79 | withIsolatedWorkingDir(t) 80 | // Ensure that UPDATE_TOOLSNAPS is not set for this test, which it might be if someone is running 81 | // UPDATE_TOOLSNAPS=true go test ./... 82 | t.Setenv("UPDATE_TOOLSNAPS", "false") 83 | 84 | // Given a non-matching snapshot file exists 85 | require.NoError(t, os.MkdirAll("__toolsnaps__", 0700)) 86 | require.NoError(t, os.WriteFile(filepath.Join("__toolsnaps__", "dummy.snap"), []byte(`{"name":"foo","value":1}`), 0600)) 87 | tool := dummyTool{"foo", 2} 88 | 89 | // When we test the snapshot 90 | err := Test("dummy", tool) 91 | 92 | // Then it should error about the schema diff 93 | require.Error(t, err) 94 | assert.Contains(t, err.Error(), "tool schema for dummy has changed unexpectedly", "expected error about diff") 95 | } 96 | 97 | func TestUpdateToolsnaps(t *testing.T) { 98 | withIsolatedWorkingDir(t) 99 | 100 | // Given UPDATE_TOOLSNAPS is set, regardless of whether a matching snapshot file exists 101 | t.Setenv("UPDATE_TOOLSNAPS", "true") 102 | require.NoError(t, os.MkdirAll("__toolsnaps__", 0700)) 103 | require.NoError(t, os.WriteFile(filepath.Join("__toolsnaps__", "dummy.snap"), []byte(`{"name":"foo","value":1}`), 0600)) 104 | tool := dummyTool{"foo", 42} 105 | 106 | // When we test the snapshot 107 | err := Test("dummy", tool) 108 | 109 | // Then it should succeed and write the snapshot file 110 | require.NoError(t, err) 111 | path := filepath.Join("__toolsnaps__", "dummy.snap") 112 | _, statErr := os.Stat(path) 113 | assert.NoError(t, statErr, "expected snapshot file to be written") 114 | } 115 | 116 | func TestMalformedSnapshotJSON(t *testing.T) { 117 | withIsolatedWorkingDir(t) 118 | // Ensure that UPDATE_TOOLSNAPS is not set for this test, which it might be if someone is running 119 | // UPDATE_TOOLSNAPS=true go test ./... 120 | t.Setenv("UPDATE_TOOLSNAPS", "false") 121 | 122 | // Given a malformed snapshot file exists 123 | require.NoError(t, os.MkdirAll("__toolsnaps__", 0700)) 124 | require.NoError(t, os.WriteFile(filepath.Join("__toolsnaps__", "dummy.snap"), []byte(`not-json`), 0600)) 125 | tool := dummyTool{"foo", 42} 126 | 127 | // When we test the snapshot 128 | err := Test("dummy", tool) 129 | 130 | // Then it should error about malformed snapshot JSON 131 | require.Error(t, err) 132 | assert.Contains(t, err.Error(), "failed to parse snapshot JSON for dummy", "expected error about malformed snapshot JSON") 133 | } 134 | -------------------------------------------------------------------------------- /pkg/errors/error.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/google/go-github/v73/github" 8 | "github.com/mark3labs/mcp-go/mcp" 9 | ) 10 | 11 | type GitHubAPIError struct { 12 | Message string `json:"message"` 13 | Response *github.Response `json:"-"` 14 | Err error `json:"-"` 15 | } 16 | 17 | // NewGitHubAPIError creates a new GitHubAPIError with the provided message, response, and error. 18 | func newGitHubAPIError(message string, resp *github.Response, err error) *GitHubAPIError { 19 | return &GitHubAPIError{ 20 | Message: message, 21 | Response: resp, 22 | Err: err, 23 | } 24 | } 25 | 26 | func (e *GitHubAPIError) Error() string { 27 | return fmt.Errorf("%s: %w", e.Message, e.Err).Error() 28 | } 29 | 30 | type GitHubGraphQLError struct { 31 | Message string `json:"message"` 32 | Err error `json:"-"` 33 | } 34 | 35 | func newGitHubGraphQLError(message string, err error) *GitHubGraphQLError { 36 | return &GitHubGraphQLError{ 37 | Message: message, 38 | Err: err, 39 | } 40 | } 41 | 42 | func (e *GitHubGraphQLError) Error() string { 43 | return fmt.Errorf("%s: %w", e.Message, e.Err).Error() 44 | } 45 | 46 | type GitHubErrorKey struct{} 47 | type GitHubCtxErrors struct { 48 | api []*GitHubAPIError 49 | graphQL []*GitHubGraphQLError 50 | } 51 | 52 | // ContextWithGitHubErrors updates or creates a context with a pointer to GitHub error information (to be used by middleware). 53 | func ContextWithGitHubErrors(ctx context.Context) context.Context { 54 | if ctx == nil { 55 | ctx = context.Background() 56 | } 57 | if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok { 58 | // If the context already has GitHubCtxErrors, we just empty the slices to start fresh 59 | val.api = []*GitHubAPIError{} 60 | val.graphQL = []*GitHubGraphQLError{} 61 | } else { 62 | // If not, we create a new GitHubCtxErrors and set it in the context 63 | ctx = context.WithValue(ctx, GitHubErrorKey{}, &GitHubCtxErrors{}) 64 | } 65 | 66 | return ctx 67 | } 68 | 69 | // GetGitHubAPIErrors retrieves the slice of GitHubAPIErrors from the context. 70 | func GetGitHubAPIErrors(ctx context.Context) ([]*GitHubAPIError, error) { 71 | if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok { 72 | return val.api, nil // return the slice of API errors from the context 73 | } 74 | return nil, fmt.Errorf("context does not contain GitHubCtxErrors") 75 | } 76 | 77 | // GetGitHubGraphQLErrors retrieves the slice of GitHubGraphQLErrors from the context. 78 | func GetGitHubGraphQLErrors(ctx context.Context) ([]*GitHubGraphQLError, error) { 79 | if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok { 80 | return val.graphQL, nil // return the slice of GraphQL errors from the context 81 | } 82 | return nil, fmt.Errorf("context does not contain GitHubCtxErrors") 83 | } 84 | 85 | func NewGitHubAPIErrorToCtx(ctx context.Context, message string, resp *github.Response, err error) (context.Context, error) { 86 | apiErr := newGitHubAPIError(message, resp, err) 87 | if ctx != nil { 88 | _, _ = addGitHubAPIErrorToContext(ctx, apiErr) // Explicitly ignore error for graceful handling 89 | } 90 | return ctx, nil 91 | } 92 | 93 | func addGitHubAPIErrorToContext(ctx context.Context, err *GitHubAPIError) (context.Context, error) { 94 | if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok { 95 | val.api = append(val.api, err) // append the error to the existing slice in the context 96 | return ctx, nil 97 | } 98 | return nil, fmt.Errorf("context does not contain GitHubCtxErrors") 99 | } 100 | 101 | func addGitHubGraphQLErrorToContext(ctx context.Context, err *GitHubGraphQLError) (context.Context, error) { 102 | if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok { 103 | val.graphQL = append(val.graphQL, err) // append the error to the existing slice in the context 104 | return ctx, nil 105 | } 106 | return nil, fmt.Errorf("context does not contain GitHubCtxErrors") 107 | } 108 | 109 | // NewGitHubAPIErrorResponse returns an mcp.NewToolResultError and retains the error in the context for access via middleware 110 | func NewGitHubAPIErrorResponse(ctx context.Context, message string, resp *github.Response, err error) *mcp.CallToolResult { 111 | apiErr := newGitHubAPIError(message, resp, err) 112 | if ctx != nil { 113 | _, _ = addGitHubAPIErrorToContext(ctx, apiErr) // Explicitly ignore error for graceful handling 114 | } 115 | return mcp.NewToolResultErrorFromErr(message, err) 116 | } 117 | 118 | // NewGitHubGraphQLErrorResponse returns an mcp.NewToolResultError and retains the error in the context for access via middleware 119 | func NewGitHubGraphQLErrorResponse(ctx context.Context, message string, err error) *mcp.CallToolResult { 120 | graphQLErr := newGitHubGraphQLError(message, err) 121 | if ctx != nil { 122 | _, _ = addGitHubGraphQLErrorToContext(ctx, graphQLErr) // Explicitly ignore error for graceful handling 123 | } 124 | return mcp.NewToolResultErrorFromErr(message, err) 125 | } 126 | -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/add_issue_comment.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Add comment to issue", 4 | "readOnlyHint": false 5 | }, 6 | "description": "Add a comment to a specific issue in a GitHub repository.", 7 | "inputSchema": { 8 | "properties": { 9 | "body": { 10 | "description": "Comment content", 11 | "type": "string" 12 | }, 13 | "issue_number": { 14 | "description": "Issue number to comment on", 15 | "type": "number" 16 | }, 17 | "owner": { 18 | "description": "Repository owner", 19 | "type": "string" 20 | }, 21 | "repo": { 22 | "description": "Repository name", 23 | "type": "string" 24 | } 25 | }, 26 | "required": [ 27 | "owner", 28 | "repo", 29 | "issue_number", 30 | "body" 31 | ], 32 | "type": "object" 33 | }, 34 | "name": "add_issue_comment" 35 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/add_pull_request_review_comment_to_pending_review.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Add comment to the requester's latest pending pull request review", 4 | "readOnlyHint": false 5 | }, 6 | "description": "Add a comment to the requester's latest pending pull request review, a pending review needs to already exist to call this (check with the user if not sure).", 7 | "inputSchema": { 8 | "properties": { 9 | "body": { 10 | "description": "The text of the review comment", 11 | "type": "string" 12 | }, 13 | "line": { 14 | "description": "The line of the blob in the pull request diff that the comment applies to. For multi-line comments, the last line of the range", 15 | "type": "number" 16 | }, 17 | "owner": { 18 | "description": "Repository owner", 19 | "type": "string" 20 | }, 21 | "path": { 22 | "description": "The relative path to the file that necessitates a comment", 23 | "type": "string" 24 | }, 25 | "pullNumber": { 26 | "description": "Pull request number", 27 | "type": "number" 28 | }, 29 | "repo": { 30 | "description": "Repository name", 31 | "type": "string" 32 | }, 33 | "side": { 34 | "description": "The side of the diff to comment on. LEFT indicates the previous state, RIGHT indicates the new state", 35 | "enum": [ 36 | "LEFT", 37 | "RIGHT" 38 | ], 39 | "type": "string" 40 | }, 41 | "startLine": { 42 | "description": "For multi-line comments, the first line of the range that the comment applies to", 43 | "type": "number" 44 | }, 45 | "startSide": { 46 | "description": "For multi-line comments, the starting side of the diff that the comment applies to. LEFT indicates the previous state, RIGHT indicates the new state", 47 | "enum": [ 48 | "LEFT", 49 | "RIGHT" 50 | ], 51 | "type": "string" 52 | }, 53 | "subjectType": { 54 | "description": "The level at which the comment is targeted", 55 | "enum": [ 56 | "FILE", 57 | "LINE" 58 | ], 59 | "type": "string" 60 | } 61 | }, 62 | "required": [ 63 | "owner", 64 | "repo", 65 | "pullNumber", 66 | "path", 67 | "body", 68 | "subjectType" 69 | ], 70 | "type": "object" 71 | }, 72 | "name": "add_pull_request_review_comment_to_pending_review" 73 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/assign_copilot_to_issue.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Assign Copilot to issue", 4 | "readOnlyHint": false, 5 | "idempotentHint": true 6 | }, 7 | "description": "Assign Copilot to a specific issue in a GitHub repository.\n\nThis tool can help with the following outcomes:\n- a Pull Request created with source code changes to resolve the issue\n\n\nMore information can be found at:\n- https://docs.github.com/en/copilot/using-github-copilot/using-copilot-coding-agent-to-work-on-tasks/about-assigning-tasks-to-copilot\n", 8 | "inputSchema": { 9 | "properties": { 10 | "issueNumber": { 11 | "description": "Issue number", 12 | "type": "number" 13 | }, 14 | "owner": { 15 | "description": "Repository owner", 16 | "type": "string" 17 | }, 18 | "repo": { 19 | "description": "Repository name", 20 | "type": "string" 21 | } 22 | }, 23 | "required": [ 24 | "owner", 25 | "repo", 26 | "issueNumber" 27 | ], 28 | "type": "object" 29 | }, 30 | "name": "assign_copilot_to_issue" 31 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/create_and_submit_pull_request_review.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Create and submit a pull request review without comments", 4 | "readOnlyHint": false 5 | }, 6 | "description": "Create and submit a review for a pull request without review comments.", 7 | "inputSchema": { 8 | "properties": { 9 | "body": { 10 | "description": "Review comment text", 11 | "type": "string" 12 | }, 13 | "commitID": { 14 | "description": "SHA of commit to review", 15 | "type": "string" 16 | }, 17 | "event": { 18 | "description": "Review action to perform", 19 | "enum": [ 20 | "APPROVE", 21 | "REQUEST_CHANGES", 22 | "COMMENT" 23 | ], 24 | "type": "string" 25 | }, 26 | "owner": { 27 | "description": "Repository owner", 28 | "type": "string" 29 | }, 30 | "pullNumber": { 31 | "description": "Pull request number", 32 | "type": "number" 33 | }, 34 | "repo": { 35 | "description": "Repository name", 36 | "type": "string" 37 | } 38 | }, 39 | "required": [ 40 | "owner", 41 | "repo", 42 | "pullNumber", 43 | "body", 44 | "event" 45 | ], 46 | "type": "object" 47 | }, 48 | "name": "create_and_submit_pull_request_review" 49 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/create_branch.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Create branch", 4 | "readOnlyHint": false 5 | }, 6 | "description": "Create a new branch in a GitHub repository", 7 | "inputSchema": { 8 | "properties": { 9 | "branch": { 10 | "description": "Name for new branch", 11 | "type": "string" 12 | }, 13 | "from_branch": { 14 | "description": "Source branch (defaults to repo default)", 15 | "type": "string" 16 | }, 17 | "owner": { 18 | "description": "Repository owner", 19 | "type": "string" 20 | }, 21 | "repo": { 22 | "description": "Repository name", 23 | "type": "string" 24 | } 25 | }, 26 | "required": [ 27 | "owner", 28 | "repo", 29 | "branch" 30 | ], 31 | "type": "object" 32 | }, 33 | "name": "create_branch" 34 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/create_issue.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Open new issue", 4 | "readOnlyHint": false 5 | }, 6 | "description": "Create a new issue in a GitHub repository.", 7 | "inputSchema": { 8 | "properties": { 9 | "assignees": { 10 | "description": "Usernames to assign to this issue", 11 | "items": { 12 | "type": "string" 13 | }, 14 | "type": "array" 15 | }, 16 | "body": { 17 | "description": "Issue body content", 18 | "type": "string" 19 | }, 20 | "labels": { 21 | "description": "Labels to apply to this issue", 22 | "items": { 23 | "type": "string" 24 | }, 25 | "type": "array" 26 | }, 27 | "milestone": { 28 | "description": "Milestone number", 29 | "type": "number" 30 | }, 31 | "owner": { 32 | "description": "Repository owner", 33 | "type": "string" 34 | }, 35 | "repo": { 36 | "description": "Repository name", 37 | "type": "string" 38 | }, 39 | "title": { 40 | "description": "Issue title", 41 | "type": "string" 42 | } 43 | }, 44 | "required": [ 45 | "owner", 46 | "repo", 47 | "title" 48 | ], 49 | "type": "object" 50 | }, 51 | "name": "create_issue" 52 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/create_or_update_file.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Create or update file", 4 | "readOnlyHint": false 5 | }, 6 | "description": "Create or update a single file in a GitHub repository. If updating, you must provide the SHA of the file you want to update. Use this tool to create or update a file in a GitHub repository remotely; do not use it for local file operations.", 7 | "inputSchema": { 8 | "properties": { 9 | "branch": { 10 | "description": "Branch to create/update the file in", 11 | "type": "string" 12 | }, 13 | "content": { 14 | "description": "Content of the file", 15 | "type": "string" 16 | }, 17 | "message": { 18 | "description": "Commit message", 19 | "type": "string" 20 | }, 21 | "owner": { 22 | "description": "Repository owner (username or organization)", 23 | "type": "string" 24 | }, 25 | "path": { 26 | "description": "Path where to create/update the file", 27 | "type": "string" 28 | }, 29 | "repo": { 30 | "description": "Repository name", 31 | "type": "string" 32 | }, 33 | "sha": { 34 | "description": "Required if updating an existing file. The blob SHA of the file being replaced.", 35 | "type": "string" 36 | } 37 | }, 38 | "required": [ 39 | "owner", 40 | "repo", 41 | "path", 42 | "content", 43 | "message", 44 | "branch" 45 | ], 46 | "type": "object" 47 | }, 48 | "name": "create_or_update_file" 49 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/create_pending_pull_request_review.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Create pending pull request review", 4 | "readOnlyHint": false 5 | }, 6 | "description": "Create a pending review for a pull request. Call this first before attempting to add comments to a pending review, and ultimately submitting it. A pending pull request review means a pull request review, it is pending because you create it first and submit it later, and the PR author will not see it until it is submitted.", 7 | "inputSchema": { 8 | "properties": { 9 | "commitID": { 10 | "description": "SHA of commit to review", 11 | "type": "string" 12 | }, 13 | "owner": { 14 | "description": "Repository owner", 15 | "type": "string" 16 | }, 17 | "pullNumber": { 18 | "description": "Pull request number", 19 | "type": "number" 20 | }, 21 | "repo": { 22 | "description": "Repository name", 23 | "type": "string" 24 | } 25 | }, 26 | "required": [ 27 | "owner", 28 | "repo", 29 | "pullNumber" 30 | ], 31 | "type": "object" 32 | }, 33 | "name": "create_pending_pull_request_review" 34 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/create_pull_request.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Open new pull request", 4 | "readOnlyHint": false 5 | }, 6 | "description": "Create a new pull request in a GitHub repository.", 7 | "inputSchema": { 8 | "properties": { 9 | "base": { 10 | "description": "Branch to merge into", 11 | "type": "string" 12 | }, 13 | "body": { 14 | "description": "PR description", 15 | "type": "string" 16 | }, 17 | "draft": { 18 | "description": "Create as draft PR", 19 | "type": "boolean" 20 | }, 21 | "head": { 22 | "description": "Branch containing changes", 23 | "type": "string" 24 | }, 25 | "maintainer_can_modify": { 26 | "description": "Allow maintainer edits", 27 | "type": "boolean" 28 | }, 29 | "owner": { 30 | "description": "Repository owner", 31 | "type": "string" 32 | }, 33 | "repo": { 34 | "description": "Repository name", 35 | "type": "string" 36 | }, 37 | "title": { 38 | "description": "PR title", 39 | "type": "string" 40 | } 41 | }, 42 | "required": [ 43 | "owner", 44 | "repo", 45 | "title", 46 | "head", 47 | "base" 48 | ], 49 | "type": "object" 50 | }, 51 | "name": "create_pull_request" 52 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/create_repository.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Create repository", 4 | "readOnlyHint": false 5 | }, 6 | "description": "Create a new GitHub repository in your account", 7 | "inputSchema": { 8 | "properties": { 9 | "autoInit": { 10 | "description": "Initialize with README", 11 | "type": "boolean" 12 | }, 13 | "description": { 14 | "description": "Repository description", 15 | "type": "string" 16 | }, 17 | "name": { 18 | "description": "Repository name", 19 | "type": "string" 20 | }, 21 | "private": { 22 | "description": "Whether repo should be private", 23 | "type": "boolean" 24 | } 25 | }, 26 | "required": [ 27 | "name" 28 | ], 29 | "type": "object" 30 | }, 31 | "name": "create_repository" 32 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/delete_file.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Delete file", 4 | "readOnlyHint": false, 5 | "destructiveHint": true 6 | }, 7 | "description": "Delete a file from a GitHub repository", 8 | "inputSchema": { 9 | "properties": { 10 | "branch": { 11 | "description": "Branch to delete the file from", 12 | "type": "string" 13 | }, 14 | "message": { 15 | "description": "Commit message", 16 | "type": "string" 17 | }, 18 | "owner": { 19 | "description": "Repository owner (username or organization)", 20 | "type": "string" 21 | }, 22 | "path": { 23 | "description": "Path to the file to delete", 24 | "type": "string" 25 | }, 26 | "repo": { 27 | "description": "Repository name", 28 | "type": "string" 29 | } 30 | }, 31 | "required": [ 32 | "owner", 33 | "repo", 34 | "path", 35 | "message", 36 | "branch" 37 | ], 38 | "type": "object" 39 | }, 40 | "name": "delete_file" 41 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/delete_pending_pull_request_review.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Delete the requester's latest pending pull request review", 4 | "readOnlyHint": false 5 | }, 6 | "description": "Delete the requester's latest pending pull request review. Use this after the user decides not to submit a pending review, if you don't know if they already created one then check first.", 7 | "inputSchema": { 8 | "properties": { 9 | "owner": { 10 | "description": "Repository owner", 11 | "type": "string" 12 | }, 13 | "pullNumber": { 14 | "description": "Pull request number", 15 | "type": "number" 16 | }, 17 | "repo": { 18 | "description": "Repository name", 19 | "type": "string" 20 | } 21 | }, 22 | "required": [ 23 | "owner", 24 | "repo", 25 | "pullNumber" 26 | ], 27 | "type": "object" 28 | }, 29 | "name": "delete_pending_pull_request_review" 30 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/dismiss_notification.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Dismiss notification", 4 | "readOnlyHint": false 5 | }, 6 | "description": "Dismiss a notification by marking it as read or done", 7 | "inputSchema": { 8 | "properties": { 9 | "state": { 10 | "description": "The new state of the notification (read/done)", 11 | "enum": [ 12 | "read", 13 | "done" 14 | ], 15 | "type": "string" 16 | }, 17 | "threadID": { 18 | "description": "The ID of the notification thread", 19 | "type": "string" 20 | } 21 | }, 22 | "required": [ 23 | "threadID" 24 | ], 25 | "type": "object" 26 | }, 27 | "name": "dismiss_notification" 28 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/fork_repository.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Fork repository", 4 | "readOnlyHint": false 5 | }, 6 | "description": "Fork a GitHub repository to your account or specified organization", 7 | "inputSchema": { 8 | "properties": { 9 | "organization": { 10 | "description": "Organization to fork to", 11 | "type": "string" 12 | }, 13 | "owner": { 14 | "description": "Repository owner", 15 | "type": "string" 16 | }, 17 | "repo": { 18 | "description": "Repository name", 19 | "type": "string" 20 | } 21 | }, 22 | "required": [ 23 | "owner", 24 | "repo" 25 | ], 26 | "type": "object" 27 | }, 28 | "name": "fork_repository" 29 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/get_code_scanning_alert.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Get code scanning alert", 4 | "readOnlyHint": true 5 | }, 6 | "description": "Get details of a specific code scanning alert in a GitHub repository.", 7 | "inputSchema": { 8 | "properties": { 9 | "alertNumber": { 10 | "description": "The number of the alert.", 11 | "type": "number" 12 | }, 13 | "owner": { 14 | "description": "The owner of the repository.", 15 | "type": "string" 16 | }, 17 | "repo": { 18 | "description": "The name of the repository.", 19 | "type": "string" 20 | } 21 | }, 22 | "required": [ 23 | "owner", 24 | "repo", 25 | "alertNumber" 26 | ], 27 | "type": "object" 28 | }, 29 | "name": "get_code_scanning_alert" 30 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/get_commit.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Get commit details", 4 | "readOnlyHint": true 5 | }, 6 | "description": "Get details for a commit from a GitHub repository", 7 | "inputSchema": { 8 | "properties": { 9 | "owner": { 10 | "description": "Repository owner", 11 | "type": "string" 12 | }, 13 | "page": { 14 | "description": "Page number for pagination (min 1)", 15 | "minimum": 1, 16 | "type": "number" 17 | }, 18 | "perPage": { 19 | "description": "Results per page for pagination (min 1, max 100)", 20 | "maximum": 100, 21 | "minimum": 1, 22 | "type": "number" 23 | }, 24 | "repo": { 25 | "description": "Repository name", 26 | "type": "string" 27 | }, 28 | "sha": { 29 | "description": "Commit SHA, branch name, or tag name", 30 | "type": "string" 31 | } 32 | }, 33 | "required": [ 34 | "owner", 35 | "repo", 36 | "sha" 37 | ], 38 | "type": "object" 39 | }, 40 | "name": "get_commit" 41 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/get_dependabot_alert.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Get dependabot alert", 4 | "readOnlyHint": true 5 | }, 6 | "description": "Get details of a specific dependabot alert in a GitHub repository.", 7 | "inputSchema": { 8 | "properties": { 9 | "alertNumber": { 10 | "description": "The number of the alert.", 11 | "type": "number" 12 | }, 13 | "owner": { 14 | "description": "The owner of the repository.", 15 | "type": "string" 16 | }, 17 | "repo": { 18 | "description": "The name of the repository.", 19 | "type": "string" 20 | } 21 | }, 22 | "required": [ 23 | "owner", 24 | "repo", 25 | "alertNumber" 26 | ], 27 | "type": "object" 28 | }, 29 | "name": "get_dependabot_alert" 30 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/get_file_contents.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Get file or directory contents", 4 | "readOnlyHint": true 5 | }, 6 | "description": "Get the contents of a file or directory from a GitHub repository", 7 | "inputSchema": { 8 | "properties": { 9 | "owner": { 10 | "description": "Repository owner (username or organization)", 11 | "type": "string" 12 | }, 13 | "path": { 14 | "default": "/", 15 | "description": "Path to file/directory (directories must end with a slash '/')", 16 | "type": "string" 17 | }, 18 | "ref": { 19 | "description": "Accepts optional git refs such as `refs/tags/{tag}`, `refs/heads/{branch}` or `refs/pull/{pr_number}/head`", 20 | "type": "string" 21 | }, 22 | "repo": { 23 | "description": "Repository name", 24 | "type": "string" 25 | }, 26 | "sha": { 27 | "description": "Accepts optional commit SHA. If specified, it will be used instead of ref", 28 | "type": "string" 29 | } 30 | }, 31 | "required": [ 32 | "owner", 33 | "repo" 34 | ], 35 | "type": "object" 36 | }, 37 | "name": "get_file_contents" 38 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/get_issue.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Get issue details", 4 | "readOnlyHint": true 5 | }, 6 | "description": "Get details of a specific issue in a GitHub repository.", 7 | "inputSchema": { 8 | "properties": { 9 | "issue_number": { 10 | "description": "The number of the issue", 11 | "type": "number" 12 | }, 13 | "owner": { 14 | "description": "The owner of the repository", 15 | "type": "string" 16 | }, 17 | "repo": { 18 | "description": "The name of the repository", 19 | "type": "string" 20 | } 21 | }, 22 | "required": [ 23 | "owner", 24 | "repo", 25 | "issue_number" 26 | ], 27 | "type": "object" 28 | }, 29 | "name": "get_issue" 30 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/get_issue_comments.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Get issue comments", 4 | "readOnlyHint": true 5 | }, 6 | "description": "Get comments for a specific issue in a GitHub repository.", 7 | "inputSchema": { 8 | "properties": { 9 | "issue_number": { 10 | "description": "Issue number", 11 | "type": "number" 12 | }, 13 | "owner": { 14 | "description": "Repository owner", 15 | "type": "string" 16 | }, 17 | "page": { 18 | "description": "Page number for pagination (min 1)", 19 | "minimum": 1, 20 | "type": "number" 21 | }, 22 | "perPage": { 23 | "description": "Results per page for pagination (min 1, max 100)", 24 | "maximum": 100, 25 | "minimum": 1, 26 | "type": "number" 27 | }, 28 | "repo": { 29 | "description": "Repository name", 30 | "type": "string" 31 | } 32 | }, 33 | "required": [ 34 | "owner", 35 | "repo", 36 | "issue_number" 37 | ], 38 | "type": "object" 39 | }, 40 | "name": "get_issue_comments" 41 | } -------------------------------------------------------------------------------- /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 is about the user's own profile for GitHub. Or when information is missing to build other tool calls.", 7 | "inputSchema": { 8 | "properties": {}, 9 | "type": "object" 10 | }, 11 | "name": "get_me" 12 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/get_notification_details.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Get notification details", 4 | "readOnlyHint": true 5 | }, 6 | "description": "Get detailed information for a specific GitHub notification, always call this tool when the user asks for details about a specific notification, if you don't know the ID list notifications first.", 7 | "inputSchema": { 8 | "properties": { 9 | "notificationID": { 10 | "description": "The ID of the notification", 11 | "type": "string" 12 | } 13 | }, 14 | "required": [ 15 | "notificationID" 16 | ], 17 | "type": "object" 18 | }, 19 | "name": "get_notification_details" 20 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/get_pull_request.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Get pull request details", 4 | "readOnlyHint": true 5 | }, 6 | "description": "Get details of a specific pull request in a GitHub repository.", 7 | "inputSchema": { 8 | "properties": { 9 | "owner": { 10 | "description": "Repository owner", 11 | "type": "string" 12 | }, 13 | "pullNumber": { 14 | "description": "Pull request number", 15 | "type": "number" 16 | }, 17 | "repo": { 18 | "description": "Repository name", 19 | "type": "string" 20 | } 21 | }, 22 | "required": [ 23 | "owner", 24 | "repo", 25 | "pullNumber" 26 | ], 27 | "type": "object" 28 | }, 29 | "name": "get_pull_request" 30 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/get_pull_request_comments.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Get pull request comments", 4 | "readOnlyHint": true 5 | }, 6 | "description": "Get comments for a specific pull request.", 7 | "inputSchema": { 8 | "properties": { 9 | "owner": { 10 | "description": "Repository owner", 11 | "type": "string" 12 | }, 13 | "pullNumber": { 14 | "description": "Pull request number", 15 | "type": "number" 16 | }, 17 | "repo": { 18 | "description": "Repository name", 19 | "type": "string" 20 | } 21 | }, 22 | "required": [ 23 | "owner", 24 | "repo", 25 | "pullNumber" 26 | ], 27 | "type": "object" 28 | }, 29 | "name": "get_pull_request_comments" 30 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/get_pull_request_diff.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Get pull request diff", 4 | "readOnlyHint": true 5 | }, 6 | "description": "Get the diff of a pull request.", 7 | "inputSchema": { 8 | "properties": { 9 | "owner": { 10 | "description": "Repository owner", 11 | "type": "string" 12 | }, 13 | "pullNumber": { 14 | "description": "Pull request number", 15 | "type": "number" 16 | }, 17 | "repo": { 18 | "description": "Repository name", 19 | "type": "string" 20 | } 21 | }, 22 | "required": [ 23 | "owner", 24 | "repo", 25 | "pullNumber" 26 | ], 27 | "type": "object" 28 | }, 29 | "name": "get_pull_request_diff" 30 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/get_pull_request_files.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Get pull request files", 4 | "readOnlyHint": true 5 | }, 6 | "description": "Get the files changed in a specific pull request.", 7 | "inputSchema": { 8 | "properties": { 9 | "owner": { 10 | "description": "Repository owner", 11 | "type": "string" 12 | }, 13 | "page": { 14 | "description": "Page number for pagination (min 1)", 15 | "minimum": 1, 16 | "type": "number" 17 | }, 18 | "perPage": { 19 | "description": "Results per page for pagination (min 1, max 100)", 20 | "maximum": 100, 21 | "minimum": 1, 22 | "type": "number" 23 | }, 24 | "pullNumber": { 25 | "description": "Pull request number", 26 | "type": "number" 27 | }, 28 | "repo": { 29 | "description": "Repository name", 30 | "type": "string" 31 | } 32 | }, 33 | "required": [ 34 | "owner", 35 | "repo", 36 | "pullNumber" 37 | ], 38 | "type": "object" 39 | }, 40 | "name": "get_pull_request_files" 41 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/get_pull_request_reviews.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Get pull request reviews", 4 | "readOnlyHint": true 5 | }, 6 | "description": "Get reviews for a specific pull request.", 7 | "inputSchema": { 8 | "properties": { 9 | "owner": { 10 | "description": "Repository owner", 11 | "type": "string" 12 | }, 13 | "pullNumber": { 14 | "description": "Pull request number", 15 | "type": "number" 16 | }, 17 | "repo": { 18 | "description": "Repository name", 19 | "type": "string" 20 | } 21 | }, 22 | "required": [ 23 | "owner", 24 | "repo", 25 | "pullNumber" 26 | ], 27 | "type": "object" 28 | }, 29 | "name": "get_pull_request_reviews" 30 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/get_pull_request_status.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Get pull request status checks", 4 | "readOnlyHint": true 5 | }, 6 | "description": "Get the status of a specific pull request.", 7 | "inputSchema": { 8 | "properties": { 9 | "owner": { 10 | "description": "Repository owner", 11 | "type": "string" 12 | }, 13 | "pullNumber": { 14 | "description": "Pull request number", 15 | "type": "number" 16 | }, 17 | "repo": { 18 | "description": "Repository name", 19 | "type": "string" 20 | } 21 | }, 22 | "required": [ 23 | "owner", 24 | "repo", 25 | "pullNumber" 26 | ], 27 | "type": "object" 28 | }, 29 | "name": "get_pull_request_status" 30 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/get_tag.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Get tag details", 4 | "readOnlyHint": true 5 | }, 6 | "description": "Get details about a specific git tag in a GitHub repository", 7 | "inputSchema": { 8 | "properties": { 9 | "owner": { 10 | "description": "Repository owner", 11 | "type": "string" 12 | }, 13 | "repo": { 14 | "description": "Repository name", 15 | "type": "string" 16 | }, 17 | "tag": { 18 | "description": "Tag name", 19 | "type": "string" 20 | } 21 | }, 22 | "required": [ 23 | "owner", 24 | "repo", 25 | "tag" 26 | ], 27 | "type": "object" 28 | }, 29 | "name": "get_tag" 30 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/list_branches.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "List branches", 4 | "readOnlyHint": true 5 | }, 6 | "description": "List branches in a GitHub repository", 7 | "inputSchema": { 8 | "properties": { 9 | "owner": { 10 | "description": "Repository owner", 11 | "type": "string" 12 | }, 13 | "page": { 14 | "description": "Page number for pagination (min 1)", 15 | "minimum": 1, 16 | "type": "number" 17 | }, 18 | "perPage": { 19 | "description": "Results per page for pagination (min 1, max 100)", 20 | "maximum": 100, 21 | "minimum": 1, 22 | "type": "number" 23 | }, 24 | "repo": { 25 | "description": "Repository name", 26 | "type": "string" 27 | } 28 | }, 29 | "required": [ 30 | "owner", 31 | "repo" 32 | ], 33 | "type": "object" 34 | }, 35 | "name": "list_branches" 36 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/list_code_scanning_alerts.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "List code scanning alerts", 4 | "readOnlyHint": true 5 | }, 6 | "description": "List code scanning alerts in a GitHub repository.", 7 | "inputSchema": { 8 | "properties": { 9 | "owner": { 10 | "description": "The owner of the repository.", 11 | "type": "string" 12 | }, 13 | "ref": { 14 | "description": "The Git reference for the results you want to list.", 15 | "type": "string" 16 | }, 17 | "repo": { 18 | "description": "The name of the repository.", 19 | "type": "string" 20 | }, 21 | "severity": { 22 | "description": "Filter code scanning alerts by severity", 23 | "enum": [ 24 | "critical", 25 | "high", 26 | "medium", 27 | "low", 28 | "warning", 29 | "note", 30 | "error" 31 | ], 32 | "type": "string" 33 | }, 34 | "state": { 35 | "default": "open", 36 | "description": "Filter code scanning alerts by state. Defaults to open", 37 | "enum": [ 38 | "open", 39 | "closed", 40 | "dismissed", 41 | "fixed" 42 | ], 43 | "type": "string" 44 | }, 45 | "tool_name": { 46 | "description": "The name of the tool used for code scanning.", 47 | "type": "string" 48 | } 49 | }, 50 | "required": [ 51 | "owner", 52 | "repo" 53 | ], 54 | "type": "object" 55 | }, 56 | "name": "list_code_scanning_alerts" 57 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/list_commits.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "List commits", 4 | "readOnlyHint": true 5 | }, 6 | "description": "Get list of commits of a branch in a GitHub repository. Returns at least 30 results per page by default, but can return more if specified using the perPage parameter (up to 100).", 7 | "inputSchema": { 8 | "properties": { 9 | "author": { 10 | "description": "Author username or email address to filter commits by", 11 | "type": "string" 12 | }, 13 | "owner": { 14 | "description": "Repository owner", 15 | "type": "string" 16 | }, 17 | "page": { 18 | "description": "Page number for pagination (min 1)", 19 | "minimum": 1, 20 | "type": "number" 21 | }, 22 | "perPage": { 23 | "description": "Results per page for pagination (min 1, max 100)", 24 | "maximum": 100, 25 | "minimum": 1, 26 | "type": "number" 27 | }, 28 | "repo": { 29 | "description": "Repository name", 30 | "type": "string" 31 | }, 32 | "sha": { 33 | "description": "Commit SHA, branch or tag name to list commits of. If not provided, uses the default branch of the repository. If a commit SHA is provided, will list commits up to that SHA.", 34 | "type": "string" 35 | } 36 | }, 37 | "required": [ 38 | "owner", 39 | "repo" 40 | ], 41 | "type": "object" 42 | }, 43 | "name": "list_commits" 44 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/list_dependabot_alerts.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "List dependabot alerts", 4 | "readOnlyHint": true 5 | }, 6 | "description": "List dependabot alerts in a GitHub repository.", 7 | "inputSchema": { 8 | "properties": { 9 | "owner": { 10 | "description": "The owner of the repository.", 11 | "type": "string" 12 | }, 13 | "repo": { 14 | "description": "The name of the repository.", 15 | "type": "string" 16 | }, 17 | "severity": { 18 | "description": "Filter dependabot alerts by severity", 19 | "enum": [ 20 | "low", 21 | "medium", 22 | "high", 23 | "critical" 24 | ], 25 | "type": "string" 26 | }, 27 | "state": { 28 | "default": "open", 29 | "description": "Filter dependabot alerts by state. Defaults to open", 30 | "enum": [ 31 | "open", 32 | "fixed", 33 | "dismissed", 34 | "auto_dismissed" 35 | ], 36 | "type": "string" 37 | } 38 | }, 39 | "required": [ 40 | "owner", 41 | "repo" 42 | ], 43 | "type": "object" 44 | }, 45 | "name": "list_dependabot_alerts" 46 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/list_issues.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "List issues", 4 | "readOnlyHint": true 5 | }, 6 | "description": "List issues in a GitHub repository.", 7 | "inputSchema": { 8 | "properties": { 9 | "direction": { 10 | "description": "Sort direction", 11 | "enum": [ 12 | "asc", 13 | "desc" 14 | ], 15 | "type": "string" 16 | }, 17 | "labels": { 18 | "description": "Filter by labels", 19 | "items": { 20 | "type": "string" 21 | }, 22 | "type": "array" 23 | }, 24 | "owner": { 25 | "description": "Repository owner", 26 | "type": "string" 27 | }, 28 | "page": { 29 | "description": "Page number for pagination (min 1)", 30 | "minimum": 1, 31 | "type": "number" 32 | }, 33 | "perPage": { 34 | "description": "Results per page for pagination (min 1, max 100)", 35 | "maximum": 100, 36 | "minimum": 1, 37 | "type": "number" 38 | }, 39 | "repo": { 40 | "description": "Repository name", 41 | "type": "string" 42 | }, 43 | "since": { 44 | "description": "Filter by date (ISO 8601 timestamp)", 45 | "type": "string" 46 | }, 47 | "sort": { 48 | "description": "Sort order", 49 | "enum": [ 50 | "created", 51 | "updated", 52 | "comments" 53 | ], 54 | "type": "string" 55 | }, 56 | "state": { 57 | "description": "Filter by state", 58 | "enum": [ 59 | "open", 60 | "closed", 61 | "all" 62 | ], 63 | "type": "string" 64 | } 65 | }, 66 | "required": [ 67 | "owner", 68 | "repo" 69 | ], 70 | "type": "object" 71 | }, 72 | "name": "list_issues" 73 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/list_notifications.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "List notifications", 4 | "readOnlyHint": true 5 | }, 6 | "description": "Lists all GitHub notifications for the authenticated user, including unread notifications, mentions, review requests, assignments, and updates on issues or pull requests. Use this tool whenever the user asks what to work on next, requests a summary of their GitHub activity, wants to see pending reviews, or needs to check for new updates or tasks. This tool is the primary way to discover actionable items, reminders, and outstanding work on GitHub. Always call this tool when asked what to work on next, what is pending, or what needs attention in GitHub.", 7 | "inputSchema": { 8 | "properties": { 9 | "before": { 10 | "description": "Only show notifications updated before the given time (ISO 8601 format)", 11 | "type": "string" 12 | }, 13 | "filter": { 14 | "description": "Filter notifications to, use default unless specified. Read notifications are ones that have already been acknowledged by the user. Participating notifications are those that the user is directly involved in, such as issues or pull requests they have commented on or created.", 15 | "enum": [ 16 | "default", 17 | "include_read_notifications", 18 | "only_participating" 19 | ], 20 | "type": "string" 21 | }, 22 | "owner": { 23 | "description": "Optional repository owner. If provided with repo, only notifications for this repository are listed.", 24 | "type": "string" 25 | }, 26 | "page": { 27 | "description": "Page number for pagination (min 1)", 28 | "minimum": 1, 29 | "type": "number" 30 | }, 31 | "perPage": { 32 | "description": "Results per page for pagination (min 1, max 100)", 33 | "maximum": 100, 34 | "minimum": 1, 35 | "type": "number" 36 | }, 37 | "repo": { 38 | "description": "Optional repository name. If provided with owner, only notifications for this repository are listed.", 39 | "type": "string" 40 | }, 41 | "since": { 42 | "description": "Only show notifications updated after the given time (ISO 8601 format)", 43 | "type": "string" 44 | } 45 | }, 46 | "type": "object" 47 | }, 48 | "name": "list_notifications" 49 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/list_pull_requests.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "List pull requests", 4 | "readOnlyHint": true 5 | }, 6 | "description": "List pull requests in a GitHub repository. If the user specifies an author, then DO NOT use this tool and use the search_pull_requests tool instead.", 7 | "inputSchema": { 8 | "properties": { 9 | "base": { 10 | "description": "Filter by base branch", 11 | "type": "string" 12 | }, 13 | "direction": { 14 | "description": "Sort direction", 15 | "enum": [ 16 | "asc", 17 | "desc" 18 | ], 19 | "type": "string" 20 | }, 21 | "head": { 22 | "description": "Filter by head user/org and branch", 23 | "type": "string" 24 | }, 25 | "owner": { 26 | "description": "Repository owner", 27 | "type": "string" 28 | }, 29 | "page": { 30 | "description": "Page number for pagination (min 1)", 31 | "minimum": 1, 32 | "type": "number" 33 | }, 34 | "perPage": { 35 | "description": "Results per page for pagination (min 1, max 100)", 36 | "maximum": 100, 37 | "minimum": 1, 38 | "type": "number" 39 | }, 40 | "repo": { 41 | "description": "Repository name", 42 | "type": "string" 43 | }, 44 | "sort": { 45 | "description": "Sort by", 46 | "enum": [ 47 | "created", 48 | "updated", 49 | "popularity", 50 | "long-running" 51 | ], 52 | "type": "string" 53 | }, 54 | "state": { 55 | "description": "Filter by state", 56 | "enum": [ 57 | "open", 58 | "closed", 59 | "all" 60 | ], 61 | "type": "string" 62 | } 63 | }, 64 | "required": [ 65 | "owner", 66 | "repo" 67 | ], 68 | "type": "object" 69 | }, 70 | "name": "list_pull_requests" 71 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/list_tags.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "List tags", 4 | "readOnlyHint": true 5 | }, 6 | "description": "List git tags in a GitHub repository", 7 | "inputSchema": { 8 | "properties": { 9 | "owner": { 10 | "description": "Repository owner", 11 | "type": "string" 12 | }, 13 | "page": { 14 | "description": "Page number for pagination (min 1)", 15 | "minimum": 1, 16 | "type": "number" 17 | }, 18 | "perPage": { 19 | "description": "Results per page for pagination (min 1, max 100)", 20 | "maximum": 100, 21 | "minimum": 1, 22 | "type": "number" 23 | }, 24 | "repo": { 25 | "description": "Repository name", 26 | "type": "string" 27 | } 28 | }, 29 | "required": [ 30 | "owner", 31 | "repo" 32 | ], 33 | "type": "object" 34 | }, 35 | "name": "list_tags" 36 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/manage_notification_subscription.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Manage notification subscription", 4 | "readOnlyHint": false 5 | }, 6 | "description": "Manage a notification subscription: ignore, watch, or delete a notification thread subscription.", 7 | "inputSchema": { 8 | "properties": { 9 | "action": { 10 | "description": "Action to perform: ignore, watch, or delete the notification subscription.", 11 | "enum": [ 12 | "ignore", 13 | "watch", 14 | "delete" 15 | ], 16 | "type": "string" 17 | }, 18 | "notificationID": { 19 | "description": "The ID of the notification thread.", 20 | "type": "string" 21 | } 22 | }, 23 | "required": [ 24 | "notificationID", 25 | "action" 26 | ], 27 | "type": "object" 28 | }, 29 | "name": "manage_notification_subscription" 30 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/manage_repository_notification_subscription.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Manage repository notification subscription", 4 | "readOnlyHint": false 5 | }, 6 | "description": "Manage a repository notification subscription: ignore, watch, or delete repository notifications subscription for the provided repository.", 7 | "inputSchema": { 8 | "properties": { 9 | "action": { 10 | "description": "Action to perform: ignore, watch, or delete the repository notification subscription.", 11 | "enum": [ 12 | "ignore", 13 | "watch", 14 | "delete" 15 | ], 16 | "type": "string" 17 | }, 18 | "owner": { 19 | "description": "The account owner of the repository.", 20 | "type": "string" 21 | }, 22 | "repo": { 23 | "description": "The name of the repository.", 24 | "type": "string" 25 | } 26 | }, 27 | "required": [ 28 | "owner", 29 | "repo", 30 | "action" 31 | ], 32 | "type": "object" 33 | }, 34 | "name": "manage_repository_notification_subscription" 35 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/mark_all_notifications_read.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Mark all notifications as read", 4 | "readOnlyHint": false 5 | }, 6 | "description": "Mark all notifications as read", 7 | "inputSchema": { 8 | "properties": { 9 | "lastReadAt": { 10 | "description": "Describes the last point that notifications were checked (optional). Default: Now", 11 | "type": "string" 12 | }, 13 | "owner": { 14 | "description": "Optional repository owner. If provided with repo, only notifications for this repository are marked as read.", 15 | "type": "string" 16 | }, 17 | "repo": { 18 | "description": "Optional repository name. If provided with owner, only notifications for this repository are marked as read.", 19 | "type": "string" 20 | } 21 | }, 22 | "type": "object" 23 | }, 24 | "name": "mark_all_notifications_read" 25 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/merge_pull_request.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Merge pull request", 4 | "readOnlyHint": false 5 | }, 6 | "description": "Merge a pull request in a GitHub repository.", 7 | "inputSchema": { 8 | "properties": { 9 | "commit_message": { 10 | "description": "Extra detail for merge commit", 11 | "type": "string" 12 | }, 13 | "commit_title": { 14 | "description": "Title for merge commit", 15 | "type": "string" 16 | }, 17 | "merge_method": { 18 | "description": "Merge method", 19 | "enum": [ 20 | "merge", 21 | "squash", 22 | "rebase" 23 | ], 24 | "type": "string" 25 | }, 26 | "owner": { 27 | "description": "Repository owner", 28 | "type": "string" 29 | }, 30 | "pullNumber": { 31 | "description": "Pull request number", 32 | "type": "number" 33 | }, 34 | "repo": { 35 | "description": "Repository name", 36 | "type": "string" 37 | } 38 | }, 39 | "required": [ 40 | "owner", 41 | "repo", 42 | "pullNumber" 43 | ], 44 | "type": "object" 45 | }, 46 | "name": "merge_pull_request" 47 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/push_files.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Push files to repository", 4 | "readOnlyHint": false 5 | }, 6 | "description": "Push multiple files to a GitHub repository in a single commit", 7 | "inputSchema": { 8 | "properties": { 9 | "branch": { 10 | "description": "Branch to push to", 11 | "type": "string" 12 | }, 13 | "files": { 14 | "description": "Array of file objects to push, each object with path (string) and content (string)", 15 | "items": { 16 | "additionalProperties": false, 17 | "properties": { 18 | "content": { 19 | "description": "file content", 20 | "type": "string" 21 | }, 22 | "path": { 23 | "description": "path to the file", 24 | "type": "string" 25 | } 26 | }, 27 | "required": [ 28 | "path", 29 | "content" 30 | ], 31 | "type": "object" 32 | }, 33 | "type": "array" 34 | }, 35 | "message": { 36 | "description": "Commit message", 37 | "type": "string" 38 | }, 39 | "owner": { 40 | "description": "Repository owner", 41 | "type": "string" 42 | }, 43 | "repo": { 44 | "description": "Repository name", 45 | "type": "string" 46 | } 47 | }, 48 | "required": [ 49 | "owner", 50 | "repo", 51 | "branch", 52 | "files", 53 | "message" 54 | ], 55 | "type": "object" 56 | }, 57 | "name": "push_files" 58 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/request_copilot_review.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Request Copilot review", 4 | "readOnlyHint": false 5 | }, 6 | "description": "Request a GitHub Copilot code review for a pull request. Use this for automated feedback on pull requests, usually before requesting a human reviewer.", 7 | "inputSchema": { 8 | "properties": { 9 | "owner": { 10 | "description": "Repository owner", 11 | "type": "string" 12 | }, 13 | "pullNumber": { 14 | "description": "Pull request number", 15 | "type": "number" 16 | }, 17 | "repo": { 18 | "description": "Repository name", 19 | "type": "string" 20 | } 21 | }, 22 | "required": [ 23 | "owner", 24 | "repo", 25 | "pullNumber" 26 | ], 27 | "type": "object" 28 | }, 29 | "name": "request_copilot_review" 30 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/search_code.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Search code", 4 | "readOnlyHint": true 5 | }, 6 | "description": "Search for code across GitHub repositories", 7 | "inputSchema": { 8 | "properties": { 9 | "order": { 10 | "description": "Sort order", 11 | "enum": [ 12 | "asc", 13 | "desc" 14 | ], 15 | "type": "string" 16 | }, 17 | "page": { 18 | "description": "Page number for pagination (min 1)", 19 | "minimum": 1, 20 | "type": "number" 21 | }, 22 | "perPage": { 23 | "description": "Results per page for pagination (min 1, max 100)", 24 | "maximum": 100, 25 | "minimum": 1, 26 | "type": "number" 27 | }, 28 | "q": { 29 | "description": "Search query using GitHub code search syntax", 30 | "type": "string" 31 | }, 32 | "sort": { 33 | "description": "Sort field ('indexed' only)", 34 | "type": "string" 35 | } 36 | }, 37 | "required": [ 38 | "q" 39 | ], 40 | "type": "object" 41 | }, 42 | "name": "search_code" 43 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/search_issues.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Search issues", 4 | "readOnlyHint": true 5 | }, 6 | "description": "Search for issues in GitHub repositories using issues search syntax already scoped to is:issue", 7 | "inputSchema": { 8 | "properties": { 9 | "order": { 10 | "description": "Sort order", 11 | "enum": [ 12 | "asc", 13 | "desc" 14 | ], 15 | "type": "string" 16 | }, 17 | "owner": { 18 | "description": "Optional repository owner. If provided with repo, only notifications for this repository are listed.", 19 | "type": "string" 20 | }, 21 | "page": { 22 | "description": "Page number for pagination (min 1)", 23 | "minimum": 1, 24 | "type": "number" 25 | }, 26 | "perPage": { 27 | "description": "Results per page for pagination (min 1, max 100)", 28 | "maximum": 100, 29 | "minimum": 1, 30 | "type": "number" 31 | }, 32 | "query": { 33 | "description": "Search query using GitHub issues search syntax", 34 | "type": "string" 35 | }, 36 | "repo": { 37 | "description": "Optional repository name. If provided with owner, only notifications for this repository are listed.", 38 | "type": "string" 39 | }, 40 | "sort": { 41 | "description": "Sort field by number of matches of categories, defaults to best match", 42 | "enum": [ 43 | "comments", 44 | "reactions", 45 | "reactions-+1", 46 | "reactions--1", 47 | "reactions-smile", 48 | "reactions-thinking_face", 49 | "reactions-heart", 50 | "reactions-tada", 51 | "interactions", 52 | "created", 53 | "updated" 54 | ], 55 | "type": "string" 56 | } 57 | }, 58 | "required": [ 59 | "query" 60 | ], 61 | "type": "object" 62 | }, 63 | "name": "search_issues" 64 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/search_pull_requests.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Search pull requests", 4 | "readOnlyHint": true 5 | }, 6 | "description": "Search for pull requests in GitHub repositories using issues search syntax already scoped to is:pr", 7 | "inputSchema": { 8 | "properties": { 9 | "order": { 10 | "description": "Sort order", 11 | "enum": [ 12 | "asc", 13 | "desc" 14 | ], 15 | "type": "string" 16 | }, 17 | "owner": { 18 | "description": "Optional repository owner. If provided with repo, only notifications for this repository are listed.", 19 | "type": "string" 20 | }, 21 | "page": { 22 | "description": "Page number for pagination (min 1)", 23 | "minimum": 1, 24 | "type": "number" 25 | }, 26 | "perPage": { 27 | "description": "Results per page for pagination (min 1, max 100)", 28 | "maximum": 100, 29 | "minimum": 1, 30 | "type": "number" 31 | }, 32 | "query": { 33 | "description": "Search query using GitHub pull request search syntax", 34 | "type": "string" 35 | }, 36 | "repo": { 37 | "description": "Optional repository name. If provided with owner, only notifications for this repository are listed.", 38 | "type": "string" 39 | }, 40 | "sort": { 41 | "description": "Sort field by number of matches of categories, defaults to best match", 42 | "enum": [ 43 | "comments", 44 | "reactions", 45 | "reactions-+1", 46 | "reactions--1", 47 | "reactions-smile", 48 | "reactions-thinking_face", 49 | "reactions-heart", 50 | "reactions-tada", 51 | "interactions", 52 | "created", 53 | "updated" 54 | ], 55 | "type": "string" 56 | } 57 | }, 58 | "required": [ 59 | "query" 60 | ], 61 | "type": "object" 62 | }, 63 | "name": "search_pull_requests" 64 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/search_repositories.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Search repositories", 4 | "readOnlyHint": true 5 | }, 6 | "description": "Search for GitHub repositories", 7 | "inputSchema": { 8 | "properties": { 9 | "page": { 10 | "description": "Page number for pagination (min 1)", 11 | "minimum": 1, 12 | "type": "number" 13 | }, 14 | "perPage": { 15 | "description": "Results per page for pagination (min 1, max 100)", 16 | "maximum": 100, 17 | "minimum": 1, 18 | "type": "number" 19 | }, 20 | "query": { 21 | "description": "Search query", 22 | "type": "string" 23 | } 24 | }, 25 | "required": [ 26 | "query" 27 | ], 28 | "type": "object" 29 | }, 30 | "name": "search_repositories" 31 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/search_users.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Search users", 4 | "readOnlyHint": true 5 | }, 6 | "description": "Search for GitHub users exclusively", 7 | "inputSchema": { 8 | "properties": { 9 | "order": { 10 | "description": "Sort order", 11 | "enum": [ 12 | "asc", 13 | "desc" 14 | ], 15 | "type": "string" 16 | }, 17 | "page": { 18 | "description": "Page number for pagination (min 1)", 19 | "minimum": 1, 20 | "type": "number" 21 | }, 22 | "perPage": { 23 | "description": "Results per page for pagination (min 1, max 100)", 24 | "maximum": 100, 25 | "minimum": 1, 26 | "type": "number" 27 | }, 28 | "query": { 29 | "description": "Search query using GitHub users search syntax scoped to type:user", 30 | "type": "string" 31 | }, 32 | "sort": { 33 | "description": "Sort field by category", 34 | "enum": [ 35 | "followers", 36 | "repositories", 37 | "joined" 38 | ], 39 | "type": "string" 40 | } 41 | }, 42 | "required": [ 43 | "query" 44 | ], 45 | "type": "object" 46 | }, 47 | "name": "search_users" 48 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/submit_pending_pull_request_review.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Submit the requester's latest pending pull request review", 4 | "readOnlyHint": false 5 | }, 6 | "description": "Submit the requester's latest pending pull request review, normally this is a final step after creating a pending review, adding comments first, unless you know that the user already did the first two steps, you should check before calling this.", 7 | "inputSchema": { 8 | "properties": { 9 | "body": { 10 | "description": "The text of the review comment", 11 | "type": "string" 12 | }, 13 | "event": { 14 | "description": "The event to perform", 15 | "enum": [ 16 | "APPROVE", 17 | "REQUEST_CHANGES", 18 | "COMMENT" 19 | ], 20 | "type": "string" 21 | }, 22 | "owner": { 23 | "description": "Repository owner", 24 | "type": "string" 25 | }, 26 | "pullNumber": { 27 | "description": "Pull request number", 28 | "type": "number" 29 | }, 30 | "repo": { 31 | "description": "Repository name", 32 | "type": "string" 33 | } 34 | }, 35 | "required": [ 36 | "owner", 37 | "repo", 38 | "pullNumber", 39 | "event" 40 | ], 41 | "type": "object" 42 | }, 43 | "name": "submit_pending_pull_request_review" 44 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/update_issue.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Edit issue", 4 | "readOnlyHint": false 5 | }, 6 | "description": "Update an existing issue in a GitHub repository.", 7 | "inputSchema": { 8 | "properties": { 9 | "assignees": { 10 | "description": "New assignees", 11 | "items": { 12 | "type": "string" 13 | }, 14 | "type": "array" 15 | }, 16 | "body": { 17 | "description": "New description", 18 | "type": "string" 19 | }, 20 | "issue_number": { 21 | "description": "Issue number to update", 22 | "type": "number" 23 | }, 24 | "labels": { 25 | "description": "New labels", 26 | "items": { 27 | "type": "string" 28 | }, 29 | "type": "array" 30 | }, 31 | "milestone": { 32 | "description": "New milestone number", 33 | "type": "number" 34 | }, 35 | "owner": { 36 | "description": "Repository owner", 37 | "type": "string" 38 | }, 39 | "repo": { 40 | "description": "Repository name", 41 | "type": "string" 42 | }, 43 | "state": { 44 | "description": "New state", 45 | "enum": [ 46 | "open", 47 | "closed" 48 | ], 49 | "type": "string" 50 | }, 51 | "title": { 52 | "description": "New title", 53 | "type": "string" 54 | } 55 | }, 56 | "required": [ 57 | "owner", 58 | "repo", 59 | "issue_number" 60 | ], 61 | "type": "object" 62 | }, 63 | "name": "update_issue" 64 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/update_pull_request.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Edit pull request", 4 | "readOnlyHint": false 5 | }, 6 | "description": "Update an existing pull request in a GitHub repository.", 7 | "inputSchema": { 8 | "properties": { 9 | "base": { 10 | "description": "New base branch name", 11 | "type": "string" 12 | }, 13 | "body": { 14 | "description": "New description", 15 | "type": "string" 16 | }, 17 | "maintainer_can_modify": { 18 | "description": "Allow maintainer edits", 19 | "type": "boolean" 20 | }, 21 | "owner": { 22 | "description": "Repository owner", 23 | "type": "string" 24 | }, 25 | "pullNumber": { 26 | "description": "Pull request number to update", 27 | "type": "number" 28 | }, 29 | "repo": { 30 | "description": "Repository name", 31 | "type": "string" 32 | }, 33 | "state": { 34 | "description": "New state", 35 | "enum": [ 36 | "open", 37 | "closed" 38 | ], 39 | "type": "string" 40 | }, 41 | "title": { 42 | "description": "New title", 43 | "type": "string" 44 | } 45 | }, 46 | "required": [ 47 | "owner", 48 | "repo", 49 | "pullNumber" 50 | ], 51 | "type": "object" 52 | }, 53 | "name": "update_pull_request" 54 | } -------------------------------------------------------------------------------- /pkg/github/__toolsnaps__/update_pull_request_branch.snap: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "title": "Update pull request branch", 4 | "readOnlyHint": false 5 | }, 6 | "description": "Update the branch of a pull request with the latest changes from the base branch.", 7 | "inputSchema": { 8 | "properties": { 9 | "expectedHeadSha": { 10 | "description": "The expected SHA of the pull request's HEAD ref", 11 | "type": "string" 12 | }, 13 | "owner": { 14 | "description": "Repository owner", 15 | "type": "string" 16 | }, 17 | "pullNumber": { 18 | "description": "Pull request number", 19 | "type": "number" 20 | }, 21 | "repo": { 22 | "description": "Repository name", 23 | "type": "string" 24 | } 25 | }, 26 | "required": [ 27 | "owner", 28 | "repo", 29 | "pullNumber" 30 | ], 31 | "type": "object" 32 | }, 33 | "name": "update_pull_request_branch" 34 | } -------------------------------------------------------------------------------- /pkg/github/context_tools.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | ghErrors "github.com/github/github-mcp-server/pkg/errors" 8 | "github.com/github/github-mcp-server/pkg/translations" 9 | "github.com/mark3labs/mcp-go/mcp" 10 | "github.com/mark3labs/mcp-go/server" 11 | ) 12 | 13 | // UserDetails contains additional fields about a GitHub user not already 14 | // present in MinimalUser. Used by get_me context tool but omitted from search_users. 15 | type UserDetails struct { 16 | Name string `json:"name,omitempty"` 17 | Company string `json:"company,omitempty"` 18 | Blog string `json:"blog,omitempty"` 19 | Location string `json:"location,omitempty"` 20 | Email string `json:"email,omitempty"` 21 | Hireable bool `json:"hireable,omitempty"` 22 | Bio string `json:"bio,omitempty"` 23 | TwitterUsername string `json:"twitter_username,omitempty"` 24 | PublicRepos int `json:"public_repos"` 25 | PublicGists int `json:"public_gists"` 26 | Followers int `json:"followers"` 27 | Following int `json:"following"` 28 | CreatedAt time.Time `json:"created_at"` 29 | UpdatedAt time.Time `json:"updated_at"` 30 | PrivateGists int `json:"private_gists,omitempty"` 31 | TotalPrivateRepos int64 `json:"total_private_repos,omitempty"` 32 | OwnedPrivateRepos int64 `json:"owned_private_repos,omitempty"` 33 | } 34 | 35 | // GetMe creates a tool to get details of the authenticated user. 36 | func GetMe(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, server.ToolHandlerFunc) { 37 | tool := mcp.NewTool("get_me", 38 | mcp.WithDescription(t("TOOL_GET_ME_DESCRIPTION", "Get details of the authenticated GitHub user. Use this when a request is about the user's own profile for GitHub. Or when information is missing to build other tool calls.")), 39 | mcp.WithToolAnnotation(mcp.ToolAnnotation{ 40 | Title: t("TOOL_GET_ME_USER_TITLE", "Get my user profile"), 41 | ReadOnlyHint: ToBoolPtr(true), 42 | }), 43 | ) 44 | 45 | type args struct{} 46 | handler := mcp.NewTypedToolHandler(func(ctx context.Context, _ mcp.CallToolRequest, _ args) (*mcp.CallToolResult, error) { 47 | client, err := getClient(ctx) 48 | if err != nil { 49 | return mcp.NewToolResultErrorFromErr("failed to get GitHub client", err), nil 50 | } 51 | 52 | user, res, err := client.Users.Get(ctx, "") 53 | if err != nil { 54 | return ghErrors.NewGitHubAPIErrorResponse(ctx, 55 | "failed to get user", 56 | res, 57 | err, 58 | ), nil 59 | } 60 | 61 | // Create minimal user representation instead of returning full user object 62 | minimalUser := MinimalUser{ 63 | Login: user.GetLogin(), 64 | ID: user.GetID(), 65 | ProfileURL: user.GetHTMLURL(), 66 | AvatarURL: user.GetAvatarURL(), 67 | Details: &UserDetails{ 68 | Name: user.GetName(), 69 | Company: user.GetCompany(), 70 | Blog: user.GetBlog(), 71 | Location: user.GetLocation(), 72 | Email: user.GetEmail(), 73 | Hireable: user.GetHireable(), 74 | Bio: user.GetBio(), 75 | TwitterUsername: user.GetTwitterUsername(), 76 | PublicRepos: user.GetPublicRepos(), 77 | PublicGists: user.GetPublicGists(), 78 | Followers: user.GetFollowers(), 79 | Following: user.GetFollowing(), 80 | CreatedAt: user.GetCreatedAt().Time, 81 | UpdatedAt: user.GetUpdatedAt().Time, 82 | PrivateGists: user.GetPrivateGists(), 83 | TotalPrivateRepos: user.GetTotalPrivateRepos(), 84 | OwnedPrivateRepos: user.GetOwnedPrivateRepos(), 85 | }, 86 | } 87 | 88 | return MarshalledTextResult(minimalUser), nil 89 | }) 90 | 91 | return tool, handler 92 | } 93 | -------------------------------------------------------------------------------- /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/v73/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 | Hireable: github.Ptr(true), 39 | TwitterUsername: github.Ptr("testuser_twitter"), 40 | Plan: &github.Plan{ 41 | Name: github.Ptr("pro"), 42 | }, 43 | } 44 | 45 | tests := []struct { 46 | name string 47 | stubbedGetClientFn GetClientFn 48 | requestArgs map[string]any 49 | expectToolError bool 50 | expectedUser *github.User 51 | expectedToolErrMsg string 52 | }{ 53 | { 54 | name: "successful get user", 55 | stubbedGetClientFn: stubGetClientFromHTTPFn( 56 | mock.NewMockedHTTPClient( 57 | mock.WithRequestMatch( 58 | mock.GetUser, 59 | mockUser, 60 | ), 61 | ), 62 | ), 63 | requestArgs: map[string]any{}, 64 | expectToolError: false, 65 | expectedUser: mockUser, 66 | }, 67 | { 68 | name: "successful get user with reason", 69 | stubbedGetClientFn: stubGetClientFromHTTPFn( 70 | mock.NewMockedHTTPClient( 71 | mock.WithRequestMatch( 72 | mock.GetUser, 73 | mockUser, 74 | ), 75 | ), 76 | ), 77 | requestArgs: map[string]any{ 78 | "reason": "Testing API", 79 | }, 80 | expectToolError: false, 81 | expectedUser: mockUser, 82 | }, 83 | { 84 | name: "getting client fails", 85 | stubbedGetClientFn: stubGetClientFnErr("expected test error"), 86 | requestArgs: map[string]any{}, 87 | expectToolError: true, 88 | expectedToolErrMsg: "failed to get GitHub client: expected test error", 89 | }, 90 | { 91 | name: "get user fails", 92 | stubbedGetClientFn: stubGetClientFromHTTPFn( 93 | mock.NewMockedHTTPClient( 94 | mock.WithRequestMatchHandler( 95 | mock.GetUser, 96 | badRequestHandler("expected test failure"), 97 | ), 98 | ), 99 | ), 100 | requestArgs: map[string]any{}, 101 | expectToolError: true, 102 | expectedToolErrMsg: "expected test failure", 103 | }, 104 | } 105 | 106 | for _, tc := range tests { 107 | t.Run(tc.name, func(t *testing.T) { 108 | _, handler := GetMe(tc.stubbedGetClientFn, translations.NullTranslationHelper) 109 | 110 | request := createMCPRequest(tc.requestArgs) 111 | result, err := handler(context.Background(), request) 112 | require.NoError(t, err) 113 | textContent := getTextResult(t, result) 114 | 115 | if tc.expectToolError { 116 | assert.True(t, result.IsError, "expected tool call result to be an error") 117 | assert.Contains(t, textContent.Text, tc.expectedToolErrMsg) 118 | return 119 | } 120 | 121 | // Unmarshal and verify the result 122 | var returnedUser MinimalUser 123 | err = json.Unmarshal([]byte(textContent.Text), &returnedUser) 124 | require.NoError(t, err) 125 | 126 | // Verify minimal user details 127 | assert.Equal(t, *tc.expectedUser.Login, returnedUser.Login) 128 | assert.Equal(t, *tc.expectedUser.HTMLURL, returnedUser.ProfileURL) 129 | 130 | // Verify user details 131 | require.NotNil(t, returnedUser.Details) 132 | assert.Equal(t, *tc.expectedUser.Name, returnedUser.Details.Name) 133 | assert.Equal(t, *tc.expectedUser.Email, returnedUser.Details.Email) 134 | assert.Equal(t, *tc.expectedUser.Bio, returnedUser.Details.Bio) 135 | assert.Equal(t, *tc.expectedUser.Company, returnedUser.Details.Company) 136 | assert.Equal(t, *tc.expectedUser.Location, returnedUser.Details.Location) 137 | assert.Equal(t, *tc.expectedUser.Hireable, returnedUser.Details.Hireable) 138 | assert.Equal(t, *tc.expectedUser.TwitterUsername, returnedUser.Details.TwitterUsername) 139 | }) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /pkg/github/search_utils.go: -------------------------------------------------------------------------------- 1 | package github 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | 10 | "github.com/google/go-github/v73/github" 11 | "github.com/mark3labs/mcp-go/mcp" 12 | ) 13 | 14 | func searchHandler( 15 | ctx context.Context, 16 | getClient GetClientFn, 17 | request mcp.CallToolRequest, 18 | searchType string, 19 | errorPrefix string, 20 | ) (*mcp.CallToolResult, error) { 21 | query, err := RequiredParam[string](request, "query") 22 | if err != nil { 23 | return mcp.NewToolResultError(err.Error()), nil 24 | } 25 | query = fmt.Sprintf("is:%s %s", searchType, query) 26 | 27 | owner, err := OptionalParam[string](request, "owner") 28 | if err != nil { 29 | return mcp.NewToolResultError(err.Error()), nil 30 | } 31 | 32 | repo, err := OptionalParam[string](request, "repo") 33 | if err != nil { 34 | return mcp.NewToolResultError(err.Error()), nil 35 | } 36 | 37 | if owner != "" && repo != "" { 38 | query = fmt.Sprintf("repo:%s/%s %s", owner, repo, query) 39 | } 40 | 41 | sort, err := OptionalParam[string](request, "sort") 42 | if err != nil { 43 | return mcp.NewToolResultError(err.Error()), nil 44 | } 45 | order, err := OptionalParam[string](request, "order") 46 | if err != nil { 47 | return mcp.NewToolResultError(err.Error()), nil 48 | } 49 | pagination, err := OptionalPaginationParams(request) 50 | if err != nil { 51 | return mcp.NewToolResultError(err.Error()), nil 52 | } 53 | 54 | opts := &github.SearchOptions{ 55 | // Default to "created" if no sort is provided, as it's a common use case. 56 | Sort: sort, 57 | Order: order, 58 | ListOptions: github.ListOptions{ 59 | Page: pagination.page, 60 | PerPage: pagination.perPage, 61 | }, 62 | } 63 | 64 | client, err := getClient(ctx) 65 | if err != nil { 66 | return nil, fmt.Errorf("%s: failed to get GitHub client: %w", errorPrefix, err) 67 | } 68 | result, resp, err := client.Search.Issues(ctx, query, opts) 69 | if err != nil { 70 | return nil, fmt.Errorf("%s: %w", errorPrefix, err) 71 | } 72 | defer func() { _ = resp.Body.Close() }() 73 | 74 | if resp.StatusCode != http.StatusOK { 75 | body, err := io.ReadAll(resp.Body) 76 | if err != nil { 77 | return nil, fmt.Errorf("%s: failed to read response body: %w", errorPrefix, err) 78 | } 79 | return mcp.NewToolResultError(fmt.Sprintf("%s: %s", errorPrefix, string(body))), nil 80 | } 81 | 82 | r, err := json.Marshal(result) 83 | if err != nil { 84 | return nil, fmt.Errorf("%s: failed to marshal response: %w", errorPrefix, err) 85 | } 86 | 87 | return mcp.NewToolResultText(string(r)), nil 88 | } 89 | -------------------------------------------------------------------------------- /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/raw/raw.go: -------------------------------------------------------------------------------- 1 | // Package raw provides a client for interacting with the GitHub raw file API 2 | package raw 3 | 4 | import ( 5 | "context" 6 | "net/http" 7 | "net/url" 8 | 9 | gogithub "github.com/google/go-github/v73/github" 10 | ) 11 | 12 | // GetRawClientFn is a function type that returns a RawClient instance. 13 | type GetRawClientFn func(context.Context) (*Client, error) 14 | 15 | // Client is a client for interacting with the GitHub raw content API. 16 | type Client struct { 17 | url *url.URL 18 | client *gogithub.Client 19 | } 20 | 21 | // NewClient creates a new instance of the raw API Client with the provided GitHub client and provided URL. 22 | func NewClient(client *gogithub.Client, rawURL *url.URL) *Client { 23 | client = gogithub.NewClient(client.Client()) 24 | client.BaseURL = rawURL 25 | return &Client{client: client, url: rawURL} 26 | } 27 | 28 | func (c *Client) newRequest(ctx context.Context, method string, urlStr string, body interface{}, opts ...gogithub.RequestOption) (*http.Request, error) { 29 | req, err := c.client.NewRequest(method, urlStr, body, opts...) 30 | if err != nil { 31 | return nil, err 32 | } 33 | req = req.WithContext(ctx) 34 | return req, nil 35 | } 36 | 37 | func (c *Client) refURL(owner, repo, ref, path string) string { 38 | if ref == "" { 39 | return c.url.JoinPath(owner, repo, "HEAD", path).String() 40 | } 41 | return c.url.JoinPath(owner, repo, ref, path).String() 42 | } 43 | 44 | func (c *Client) URLFromOpts(opts *ContentOpts, owner, repo, path string) string { 45 | if opts == nil { 46 | opts = &ContentOpts{} 47 | } 48 | if opts.SHA != "" { 49 | return c.commitURL(owner, repo, opts.SHA, path) 50 | } 51 | return c.refURL(owner, repo, opts.Ref, path) 52 | } 53 | 54 | // BlobURL returns the URL for a blob in the raw content API. 55 | func (c *Client) commitURL(owner, repo, sha, path string) string { 56 | return c.url.JoinPath(owner, repo, sha, path).String() 57 | } 58 | 59 | type ContentOpts struct { 60 | Ref string 61 | SHA string 62 | } 63 | 64 | // GetRawContent fetches the raw content of a file from a GitHub repository. 65 | func (c *Client) GetRawContent(ctx context.Context, owner, repo, path string, opts *ContentOpts) (*http.Response, error) { 66 | url := c.URLFromOpts(opts, owner, repo, path) 67 | req, err := c.newRequest(ctx, "GET", url, nil) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | return c.client.Client().Do(req) 73 | } 74 | -------------------------------------------------------------------------------- /pkg/raw/raw_mock.go: -------------------------------------------------------------------------------- 1 | package raw 2 | 3 | import "github.com/migueleliasweb/go-github-mock/src/mock" 4 | 5 | var GetRawReposContentsByOwnerByRepoByPath mock.EndpointPattern = mock.EndpointPattern{ 6 | Pattern: "/{owner}/{repo}/HEAD/{path:.*}", 7 | Method: "GET", 8 | } 9 | var GetRawReposContentsByOwnerByRepoByBranchByPath mock.EndpointPattern = mock.EndpointPattern{ 10 | Pattern: "/{owner}/{repo}/refs/heads/{branch}/{path:.*}", 11 | Method: "GET", 12 | } 13 | var GetRawReposContentsByOwnerByRepoByTagByPath mock.EndpointPattern = mock.EndpointPattern{ 14 | Pattern: "/{owner}/{repo}/refs/tags/{tag}/{path:.*}", 15 | Method: "GET", 16 | } 17 | var GetRawReposContentsByOwnerByRepoBySHAByPath mock.EndpointPattern = mock.EndpointPattern{ 18 | Pattern: "/{owner}/{repo}/{sha}/{path:.*}", 19 | Method: "GET", 20 | } 21 | -------------------------------------------------------------------------------- /pkg/raw/raw_test.go: -------------------------------------------------------------------------------- 1 | package raw 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "net/url" 7 | "testing" 8 | 9 | "github.com/google/go-github/v73/github" 10 | "github.com/migueleliasweb/go-github-mock/src/mock" 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestGetRawContent(t *testing.T) { 15 | base, _ := url.Parse("https://raw.example.com/") 16 | 17 | tests := []struct { 18 | name string 19 | pattern mock.EndpointPattern 20 | opts *ContentOpts 21 | owner, repo, path string 22 | statusCode int 23 | contentType string 24 | body string 25 | expectError string 26 | }{ 27 | { 28 | name: "HEAD fetch success", 29 | pattern: GetRawReposContentsByOwnerByRepoByPath, 30 | opts: nil, 31 | owner: "octocat", repo: "hello", path: "README.md", 32 | statusCode: 200, 33 | contentType: "text/plain", 34 | body: "# Test file", 35 | }, 36 | { 37 | name: "branch fetch success", 38 | pattern: GetRawReposContentsByOwnerByRepoByBranchByPath, 39 | opts: &ContentOpts{Ref: "refs/heads/main"}, 40 | owner: "octocat", repo: "hello", path: "README.md", 41 | statusCode: 200, 42 | contentType: "text/plain", 43 | body: "# Test file", 44 | }, 45 | { 46 | name: "tag fetch success", 47 | pattern: GetRawReposContentsByOwnerByRepoByTagByPath, 48 | opts: &ContentOpts{Ref: "refs/tags/v1.0.0"}, 49 | owner: "octocat", repo: "hello", path: "README.md", 50 | statusCode: 200, 51 | contentType: "text/plain", 52 | body: "# Test file", 53 | }, 54 | { 55 | name: "sha fetch success", 56 | pattern: GetRawReposContentsByOwnerByRepoBySHAByPath, 57 | opts: &ContentOpts{SHA: "abc123"}, 58 | owner: "octocat", repo: "hello", path: "README.md", 59 | statusCode: 200, 60 | contentType: "text/plain", 61 | body: "# Test file", 62 | }, 63 | { 64 | name: "not found", 65 | pattern: GetRawReposContentsByOwnerByRepoByPath, 66 | opts: nil, 67 | owner: "octocat", repo: "hello", path: "notfound.txt", 68 | statusCode: 404, 69 | contentType: "application/json", 70 | body: `{"message": "Not Found"}`, 71 | }, 72 | } 73 | 74 | for _, tc := range tests { 75 | t.Run(tc.name, func(t *testing.T) { 76 | mockedClient := mock.NewMockedHTTPClient( 77 | mock.WithRequestMatchHandler( 78 | tc.pattern, 79 | http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 80 | w.Header().Set("Content-Type", tc.contentType) 81 | w.WriteHeader(tc.statusCode) 82 | _, err := w.Write([]byte(tc.body)) 83 | require.NoError(t, err) 84 | }), 85 | ), 86 | ) 87 | ghClient := github.NewClient(mockedClient) 88 | client := NewClient(ghClient, base) 89 | resp, err := client.GetRawContent(context.Background(), tc.owner, tc.repo, tc.path, tc.opts) 90 | defer func() { 91 | _ = resp.Body.Close() 92 | }() 93 | if tc.expectError != "" { 94 | require.Error(t, err) 95 | return 96 | } 97 | require.NoError(t, err) 98 | require.Equal(t, tc.statusCode, resp.StatusCode) 99 | }) 100 | } 101 | } 102 | 103 | func TestUrlFromOpts(t *testing.T) { 104 | base, _ := url.Parse("https://raw.example.com/") 105 | ghClient := github.NewClient(nil) 106 | client := NewClient(ghClient, base) 107 | 108 | tests := []struct { 109 | name string 110 | opts *ContentOpts 111 | owner string 112 | repo string 113 | path string 114 | want string 115 | }{ 116 | { 117 | name: "no opts (HEAD)", 118 | opts: nil, 119 | owner: "octocat", repo: "hello", path: "README.md", 120 | want: "https://raw.example.com/octocat/hello/HEAD/README.md", 121 | }, 122 | { 123 | name: "ref branch", 124 | opts: &ContentOpts{Ref: "refs/heads/main"}, 125 | owner: "octocat", repo: "hello", path: "README.md", 126 | want: "https://raw.example.com/octocat/hello/refs/heads/main/README.md", 127 | }, 128 | { 129 | name: "ref tag", 130 | opts: &ContentOpts{Ref: "refs/tags/v1.0.0"}, 131 | owner: "octocat", repo: "hello", path: "README.md", 132 | want: "https://raw.example.com/octocat/hello/refs/tags/v1.0.0/README.md", 133 | }, 134 | { 135 | name: "sha", 136 | opts: &ContentOpts{SHA: "abc123"}, 137 | owner: "octocat", repo: "hello", path: "README.md", 138 | want: "https://raw.example.com/octocat/hello/abc123/README.md", 139 | }, 140 | } 141 | 142 | for _, tt := range tests { 143 | t.Run(tt.name, func(t *testing.T) { 144 | got := client.URLFromOpts(tt.opts, tt.owner, tt.repo, tt.path) 145 | if got != tt.want { 146 | t.Errorf("UrlFromOpts() = %q, want %q", got, tt.want) 147 | } 148 | }) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /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/generate-docs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script generates documentation for the GitHub MCP server. 4 | # It needs to be run after tool updates to ensure the latest changes are reflected in the documentation. 5 | go run ./cmd/github-mcp-server generate-docs -------------------------------------------------------------------------------- /script/get-discussions: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # echo '{"jsonrpc":"2.0","id":3,"params":{"name":"list_discussions","arguments": {"owner": "github", "repo": "securitylab", "first": 10, "since": "2025-04-01T00:00:00Z"}},"method":"tools/call"}' | go run cmd/github-mcp-server/main.go stdio | jq . 4 | echo '{"jsonrpc":"2.0","id":3,"params":{"name":"list_discussions","arguments": {"owner": "github", "repo": "securitylab", "first": 10, "since": "2025-04-01T00:00:00Z", "sort": "CREATED_AT", "direction": "DESC"}},"method":"tools/call"}' | go run cmd/github-mcp-server/main.go stdio | jq . 5 | 6 | -------------------------------------------------------------------------------- /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/lint: -------------------------------------------------------------------------------- 1 | set -eu 2 | 3 | # first run go fmt 4 | gofmt -s -w . 5 | 6 | BINDIR="$(git rev-parse --show-toplevel)"/bin 7 | BINARY=$BINDIR/golangci-lint 8 | GOLANGCI_LINT_VERSION=v2.2.1 9 | 10 | if [ ! -f "$BINARY" ]; then 11 | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s "$GOLANGCI_LINT_VERSION" 12 | fi 13 | 14 | $BINARY run -------------------------------------------------------------------------------- /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 <<EOF 11 | Usage: $(basename "$0") [file] 12 | 13 | If [file] is provided, input is read from that file. 14 | If no argument is given, input is read from stdin. 15 | 16 | Options: 17 | -h, --help Show this help message and exit 18 | EOF 19 | } 20 | 21 | # choose color for stdin or stdout if we are printing to 22 | # an actual terminal 23 | color(){ 24 | io="$1" 25 | if [[ "$io" == "stdin" ]]; then 26 | color="\033[0;32m" # green 27 | else 28 | color="\033[0;36m" # cyan 29 | fi 30 | if [ ! $is_terminal = "1" ]; then 31 | color="" 32 | fi 33 | echo -e "${color}[$io]" 34 | } 35 | 36 | # reset code if we are printing to an actual terminal 37 | reset(){ 38 | if [ ! $is_terminal = "1" ]; then 39 | return 40 | fi 41 | echo -e "\033[0m" 42 | } 43 | 44 | 45 | # Handle -h or --help 46 | if [[ "$1" == "-h" || "$1" == "--help" ]]; then 47 | show_help 48 | exit 0 49 | fi 50 | 51 | # Determine input source 52 | if [[ -n "$1" ]]; then 53 | if [[ ! -r "$1" ]]; then 54 | echo "Error: File '$1' not found or not readable." >&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 | -------------------------------------------------------------------------------- /script/tag-release: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit immediately if a command exits with a non-zero status. 4 | set -e 5 | 6 | # Initialize variables 7 | TAG="" 8 | DRY_RUN=false 9 | 10 | # Parse arguments 11 | for arg in "$@"; do 12 | case $arg in 13 | --dry-run) 14 | DRY_RUN=true 15 | ;; 16 | *) 17 | # The first non-flag argument is the tag 18 | if [[ ! $arg == --* ]]; then 19 | if [ -z "$TAG" ]; then 20 | TAG=$arg 21 | fi 22 | fi 23 | ;; 24 | esac 25 | done 26 | 27 | if [ "$DRY_RUN" = true ]; then 28 | echo "DRY RUN: No changes will be pushed to the remote repository." 29 | echo 30 | fi 31 | 32 | # 1. Validate input 33 | if [ -z "$TAG" ]; then 34 | echo "Error: No tag specified." 35 | echo "Usage: ./script/tag-release vX.Y.Z [--dry-run]" 36 | exit 1 37 | fi 38 | 39 | # Regular expression for semantic versioning (vX.Y.Z or vX.Y.Z-suffix) 40 | if [[ ! $TAG =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-.*)?$ ]]; then 41 | echo "Error: Tag must be in format vX.Y.Z or vX.Y.Z-suffix (e.g., v1.0.0 or v1.0.0-rc1)" 42 | exit 1 43 | fi 44 | 45 | # 2. Check current branch 46 | CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD) 47 | if [ "$CURRENT_BRANCH" != "main" ]; then 48 | echo "Error: You must be on the 'main' branch to create a release." 49 | echo "Current branch is '$CURRENT_BRANCH'." 50 | exit 1 51 | fi 52 | 53 | # 3. Fetch latest from origin 54 | echo "Fetching latest changes from origin..." 55 | git fetch origin main 56 | 57 | # 4. Check if the working directory is clean 58 | if ! git diff-index --quiet HEAD --; then 59 | echo "Error: Working directory is not clean. Please commit or stash your changes." 60 | exit 1 61 | fi 62 | 63 | # 5. Check if main is up-to-date with origin/main 64 | LOCAL_SHA=$(git rev-parse @) 65 | REMOTE_SHA=$(git rev-parse @{u}) 66 | 67 | if [ "$LOCAL_SHA" != "$REMOTE_SHA" ]; then 68 | echo "Error: Your local 'main' branch is not up-to-date with 'origin/main'. Please pull the latest changes." 69 | exit 1 70 | fi 71 | echo "✅ Local 'main' branch is up-to-date with 'origin/main'." 72 | 73 | # 6. Check if tag already exists 74 | if git tag -l | grep -q "^${TAG}quot;; then 75 | echo "Error: Tag ${TAG} already exists locally." 76 | exit 1 77 | fi 78 | if git ls-remote --tags origin | grep -q "refs/tags/${TAG}quot;; then 79 | echo "Error: Tag ${TAG} already exists on remote 'origin'." 80 | exit 1 81 | fi 82 | 83 | # 7. Confirm release with user 84 | echo 85 | LATEST_TAG=$(git tag --sort=-version:refname | head -n 1) 86 | if [ -n "$LATEST_TAG" ]; then 87 | echo "Current latest release: $LATEST_TAG" 88 | fi 89 | echo "Proposed new release: $TAG" 90 | echo 91 | read -p "Do you want to proceed with the release? (y/n) " -n 1 -r 92 | echo # Move to a new line 93 | if [[ ! $REPLY =~ ^[Yy]$ ]]; then 94 | echo "Release cancelled." 95 | exit 1 96 | fi 97 | echo 98 | 99 | # 8. Create the new release tag 100 | if [ "$DRY_RUN" = true ]; then 101 | echo "DRY RUN: Skipping creation of tag $TAG." 102 | else 103 | echo "Creating new release tag: $TAG" 104 | git tag -a "$TAG" -m "Release $TAG" 105 | fi 106 | 107 | # 9. Push the new tag to the remote repository 108 | if [ "$DRY_RUN" = true ]; then 109 | echo "DRY RUN: Skipping push of tag $TAG to origin." 110 | else 111 | echo "Pushing tag $TAG to origin..." 112 | git push origin "$TAG" 113 | fi 114 | 115 | # 10. Update and push the 'latest-release' tag 116 | if [ "$DRY_RUN" = true ]; then 117 | echo "DRY RUN: Skipping update and push of 'latest-release' tag." 118 | else 119 | echo "Updating 'latest-release' tag to point to $TAG..." 120 | git tag -f latest-release "$TAG" 121 | echo "Pushing 'latest-release' tag to origin..." 122 | git push origin latest-release --force 123 | fi 124 | 125 | if [ "$DRY_RUN" = true ]; then 126 | echo "✅ DRY RUN complete. No tags were created or pushed." 127 | else 128 | echo "✅ Successfully tagged and pushed release $TAG." 129 | echo "✅ 'latest-release' tag has been updated." 130 | fi 131 | 132 | # 11. Post-release instructions 133 | REPO_URL=$(git remote get-url origin) 134 | REPO_SLUG=$(echo "$REPO_URL" | sed -e 's/.*github.com[:\/]//' -e 's/\.git$//') 135 | 136 | cat << EOF 137 | 138 | ## 🎉 Release $TAG has been initiated! 139 | 140 | ### Next steps: 141 | 1. 📋 Check https://github.com/$REPO_SLUG/releases and wait for the draft release to show up (after the goreleaser workflow completes) 142 | 2. ✏️ Edit the new release, delete the existing notes and click the auto-generate button GitHub provides 143 | 3. ✨ Add a section at the top calling out the main features 144 | 4. 🚀 Publish the release 145 | 5. 📢 Post message in #gh-mcp-releases channel in Slack and then share to the other mcp channels 146 | 147 | ### Resources: 148 | - 📦 Draft Release: https://github.com/$REPO_SLUG/releases/tag/$TAG 149 | 150 | The release process is now ready for your review and completion! 151 | EOF 152 | -------------------------------------------------------------------------------- /script/test: -------------------------------------------------------------------------------- 1 | set -eu 2 | 3 | go test -race ./... -------------------------------------------------------------------------------- /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/v71/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-github/v73/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/gorilla/mux/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2018 The Gorilla 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/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/migueleliasweb/go-github-mock/src/mock/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Miguel Elias dos Santos 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 <mark.sagikazar@gmail.com> 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 <https://yosida95.com/>. 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/golang.org/x/time/rate/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go 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/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 | --------------------------------------------------------------------------------