diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index cd0b3f1dd..e35f807d5 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -33,6 +33,13 @@ Fixes # - [ ] Auth / permissions considered - [ ] Data exposure, filtering, or token/size limits considered +## Tool renaming +- [ ] I am renaming tools as part of this PR (e.g. a part of a consolidation effort) + - [ ] I have added the new tool aliases in `deprecated_tool_aliases.go` +- [ ] I am not renaming tools as part of this PR + +Note: if you're renaming tools, you *must* add the tool aliases. For more information on how to do so, please refer to the [official docs](https://github.com/github/github-mcp-server/blob/main/docs/tool-renaming.md). + ## Lint & tests - [ ] Linted locally with `./script/lint` diff --git a/.github/workflows/code-scanning.yml b/.github/workflows/code-scanning.yml index 7dda8c9bd..02c19fc77 100644 --- a/.github/workflows/code-scanning.yml +++ b/.github/workflows/code-scanning.yml @@ -14,6 +14,8 @@ env: jobs: analyze: name: Analyze (${{ matrix.language }}) + # Only run on the main repository, not on forks + if: github.repository == 'github/github-mcp-server' runs-on: ${{ fromJSON(matrix.runner) }} permissions: actions: read @@ -46,6 +48,9 @@ jobs: queries: "" # Default query suite packs: github/ccr-${{ matrix.language }}-queries config: | + paths-ignore: + - third-party + - third-party-licenses.*.md default-setup: org: model-packs: [ ${{ github.event.inputs.code_scanning_codeql_packs }} ] diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index ee63b9a87..43eca9fad 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -54,7 +54,7 @@ jobs: # multi-platform images and export cache # https://github.com/docker/setup-buildx-action - name: Set up Docker Buildx - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 # Login against a Docker registry except on PR # https://github.com/docker/login-action @@ -70,7 +70,7 @@ jobs: # https://github.com/docker/metadata-action - name: Extract Docker metadata id: meta - uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0 + uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | diff --git a/.github/workflows/license-check.yml b/.github/workflows/license-check.yml index d9cb59fb7..ce2fa26fb 100644 --- a/.github/workflows/license-check.yml +++ b/.github/workflows/license-check.yml @@ -1,9 +1,22 @@ -# Create a github action that runs the license check script and fails if it exits with a non-zero status +# Automatically fix license files on PRs that need updates +# Tries to auto-commit the fix, or comments with instructions if push fails name: License Check -on: [push, pull_request] +on: + pull_request: + branches: + - main # Only run when PR targets main + paths: + - "**.go" + - go.mod + - go.sum + - ".github/licenses.tmpl" + - "script/licenses*" + - "third-party-licenses.*.md" + - "third-party/**" permissions: - contents: read + contents: write + pull-requests: write jobs: license-check: @@ -12,10 +25,85 @@ jobs: steps: - name: Check out code uses: actions/checkout@v6 + with: + ref: ${{ github.head_ref }} - name: Set up Go uses: actions/setup-go@v6 with: go-version-file: "go.mod" - - name: check licenses - run: ./script/licenses-check + + # actions/setup-go does not setup the installed toolchain to be preferred over the system install, + # which causes go-licenses to raise "Package ... does not have module info" errors. + # For more information, https://github.com/google/go-licenses/issues/244#issuecomment-1885098633 + - name: Regenerate licenses + env: + CI: "true" + run: | + export GOROOT=$(go env GOROOT) + export PATH=${GOROOT}/bin:$PATH + ./script/licenses + + - name: Check for changes + id: changes + continue-on-error: true + run: script/licenses-check + + - name: Commit and push fixes + if: steps.changes.outcome == 'failure' + continue-on-error: true + id: push + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add third-party-licenses.*.md third-party/ + git commit -m "chore: regenerate license files" -m "Auto-generated by license-check workflow" + git push + + - name: Check if already commented + if: steps.changes.outcome == 'failure' && steps.push.outcome == 'failure' + id: check_comment + uses: actions/github-script@v7 + with: + script: | + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number + }); + + const alreadyCommented = comments.some(comment => + comment.user.login === 'github-actions[bot]' && + comment.body.includes('## ⚠️ License files need updating') + ); + + core.setOutput('already_commented', alreadyCommented ? 'true' : 'false'); + + - name: Comment with instructions if cannot push + if: steps.changes.outcome == 'failure' && steps.push.outcome == 'failure' && steps.check_comment.outputs.already_commented == 'false' + uses: actions/github-script@v7 + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: `## ⚠️ License files need updating + + The license files are out of date. I tried to fix them automatically but don't have permission to push to this branch. + + **Please run:** + \`\`\`bash + script/licenses + git add third-party-licenses.*.md third-party/ + git commit -m "chore: regenerate license files" + git push + \`\`\` + + Alternatively, enable "Allow edits by maintainers" in the PR settings so I can fix it automatically.` + }); + + - name: Fail check if changes needed + if: steps.changes.outcome == 'failure' + run: exit 1 + diff --git a/README.md b/README.md index ce6eb81cb..e2b5af23a 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ Alternatively, to manually configure VS Code, choose the appropriate JSON block ### Install in other MCP hosts - **[GitHub Copilot in other IDEs](/docs/installation-guides/install-other-copilot-ides.md)** - Installation for JetBrains, Visual Studio, Eclipse, and Xcode with GitHub Copilot -- **[Claude Applications](/docs/installation-guides/install-claude.md)** - Installation guide for Claude Web, Claude Desktop and Claude Code CLI +- **[Claude Applications](/docs/installation-guides/install-claude.md)** - Installation guide for Claude Desktop and Claude Code CLI - **[Cursor](/docs/installation-guides/install-cursor.md)** - Installation guide for Cursor IDE - **[Windsurf](/docs/installation-guides/install-windsurf.md)** - Installation guide for Windsurf IDE @@ -393,7 +393,7 @@ When using Docker, you can pass the toolsets as environment variables: ```bash docker run -i --rm \ -e GITHUB_PERSONAL_ACCESS_TOKEN= \ - -e GITHUB_TOOLSETS="repos,issues,pull_requests,actions,code_security,experiments" \ + -e GITHUB_TOOLSETS="repos,issues,pull_requests,actions,code_security" \ ghcr.io/github/github-mcp-server ``` diff --git a/go.mod b/go.mod index 9423ce557..691a949bd 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,12 @@ go 1.24.0 require ( github.com/google/go-github/v79 v79.0.0 - github.com/google/jsonschema-go v0.3.0 + github.com/google/jsonschema-go v0.4.2 github.com/josephburnett/jd v1.9.2 github.com/microcosm-cc/bluemonday v1.0.27 github.com/migueleliasweb/go-github-mock v1.3.0 github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021 - github.com/spf13/cobra v1.10.1 + github.com/spf13/cobra v1.10.2 github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 ) @@ -37,7 +37,7 @@ require ( github.com/go-viper/mapstructure/v2 v2.4.0 github.com/google/go-querystring v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/modelcontextprotocol/go-sdk v1.2.0-pre.1 + github.com/modelcontextprotocol/go-sdk v1.2.0 github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect diff --git a/go.sum b/go.sum index fc0980ab1..6f38bea2f 100644 --- a/go.sum +++ b/go.sum @@ -28,8 +28,8 @@ github.com/google/go-github/v79 v79.0.0 h1:MdodQojuFPBhmtwHiBcIGLw/e/wei2PvFX9nd github.com/google/go-github/v79 v79.0.0/go.mod h1:OAFbNhq7fQwohojb06iIIQAB9CBGYLq999myfUFnrS4= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q= -github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= +github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= +github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= @@ -57,8 +57,8 @@ github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwX github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/migueleliasweb/go-github-mock v1.3.0 h1:2sVP9JEMB2ubQw1IKto3/fzF51oFC6eVWOOFDgQoq88= github.com/migueleliasweb/go-github-mock v1.3.0/go.mod h1:ipQhV8fTcj/G6m7BKzin08GaJ/3B5/SonRAkgrk0zCY= -github.com/modelcontextprotocol/go-sdk v1.2.0-pre.1 h1:14+JrlEIFvUmbu5+iJzWPLk8CkpvegfKr42oXyjp3O4= -github.com/modelcontextprotocol/go-sdk v1.2.0-pre.1/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10= +github.com/modelcontextprotocol/go-sdk v1.2.0 h1:Y23co09300CEk8iZ/tMxIX1dVmKZkzoSBZOpJwUnc/s= +github.com/modelcontextprotocol/go-sdk v1.2.0/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10= github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021 h1:31Y+Yu373ymebRdJN1cWLLooHH8xAr0MhKTEJGV/87g= github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021/go.mod h1:WERUkUryfUWlrHnFSO/BEUZ+7Ns8aZy7iVOGewxKzcc= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= @@ -82,8 +82,8 @@ github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= -github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= -github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= diff --git a/pkg/errors/error.go b/pkg/errors/error.go index 095f8d5b7..d17fedd92 100644 --- a/pkg/errors/error.go +++ b/pkg/errors/error.go @@ -3,6 +3,7 @@ package errors import ( "context" "fmt" + "net/http" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" @@ -44,10 +45,29 @@ func (e *GitHubGraphQLError) Error() string { return fmt.Errorf("%s: %w", e.Message, e.Err).Error() } +type GitHubRawAPIError struct { + Message string `json:"message"` + Response *http.Response `json:"-"` + Err error `json:"-"` +} + +func newGitHubRawAPIError(message string, resp *http.Response, err error) *GitHubRawAPIError { + return &GitHubRawAPIError{ + Message: message, + Response: resp, + Err: err, + } +} + +func (e *GitHubRawAPIError) Error() string { + return fmt.Errorf("%s: %w", e.Message, e.Err).Error() +} + type GitHubErrorKey struct{} type GitHubCtxErrors struct { api []*GitHubAPIError graphQL []*GitHubGraphQLError + raw []*GitHubRawAPIError } // ContextWithGitHubErrors updates or creates a context with a pointer to GitHub error information (to be used by middleware). @@ -59,6 +79,7 @@ func ContextWithGitHubErrors(ctx context.Context) context.Context { // If the context already has GitHubCtxErrors, we just empty the slices to start fresh val.api = []*GitHubAPIError{} val.graphQL = []*GitHubGraphQLError{} + val.raw = []*GitHubRawAPIError{} } else { // If not, we create a new GitHubCtxErrors and set it in the context ctx = context.WithValue(ctx, GitHubErrorKey{}, &GitHubCtxErrors{}) @@ -83,6 +104,14 @@ func GetGitHubGraphQLErrors(ctx context.Context) ([]*GitHubGraphQLError, error) return nil, fmt.Errorf("context does not contain GitHubCtxErrors") } +// GetGitHubRawAPIErrors retrieves the slice of GitHubRawAPIErrors from the context. +func GetGitHubRawAPIErrors(ctx context.Context) ([]*GitHubRawAPIError, error) { + if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok { + return val.raw, nil // return the slice of raw API errors from the context + } + return nil, fmt.Errorf("context does not contain GitHubCtxErrors") +} + func NewGitHubAPIErrorToCtx(ctx context.Context, message string, resp *github.Response, err error) (context.Context, error) { apiErr := newGitHubAPIError(message, resp, err) if ctx != nil { @@ -107,6 +136,15 @@ func addGitHubGraphQLErrorToContext(ctx context.Context, err *GitHubGraphQLError return nil, fmt.Errorf("context does not contain GitHubCtxErrors") } +func addRawAPIErrorToContext(ctx context.Context, err *GitHubRawAPIError) (context.Context, error) { + if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok { + val.raw = append(val.raw, err) + return ctx, nil + } + + return nil, fmt.Errorf("context does not contain GitHubCtxErrors") +} + // NewGitHubAPIErrorResponse returns an mcp.NewToolResultError and retains the error in the context for access via middleware func NewGitHubAPIErrorResponse(ctx context.Context, message string, resp *github.Response, err error) *mcp.CallToolResult { apiErr := newGitHubAPIError(message, resp, err) @@ -125,6 +163,15 @@ func NewGitHubGraphQLErrorResponse(ctx context.Context, message string, err erro return utils.NewToolResultErrorFromErr(message, err) } +// NewGitHubRawAPIErrorResponse returns an mcp.NewToolResultError and retains the error in the context for access via middleware +func NewGitHubRawAPIErrorResponse(ctx context.Context, message string, resp *http.Response, err error) *mcp.CallToolResult { + rawErr := newGitHubRawAPIError(message, resp, err) + if ctx != nil { + _, _ = addRawAPIErrorToContext(ctx, rawErr) // Explicitly ignore error for graceful handling + } + return utils.NewToolResultErrorFromErr(message, err) +} + // NewGitHubAPIStatusErrorResponse handles cases where the API call succeeds (err == nil) // but returns an unexpected HTTP status code. It creates a synthetic error from the // status code and response body, then records it in context for observability tracking. diff --git a/pkg/errors/error_test.go b/pkg/errors/error_test.go index b5ef40596..072a09a28 100644 --- a/pkg/errors/error_test.go +++ b/pkg/errors/error_test.go @@ -63,6 +63,33 @@ func TestGitHubErrorContext(t *testing.T) { assert.Equal(t, "failed to execute mutation: GraphQL query failed", gqlError.Error()) }) + t.Run("Raw API errors can be added to context and retrieved", func(t *testing.T) { + // Given a context with GitHub error tracking enabled + ctx := ContextWithGitHubErrors(context.Background()) + + // Create a mock HTTP response + resp := &http.Response{ + StatusCode: 404, + Status: "404 Not Found", + } + originalErr := fmt.Errorf("raw content not found") + + // When we add a raw API error to the context + rawAPIErr := newGitHubRawAPIError("failed to fetch raw content", resp, originalErr) + updatedCtx, err := addRawAPIErrorToContext(ctx, rawAPIErr) + require.NoError(t, err) + + // Then we should be able to retrieve the error from the updated context + rawErrors, err := GetGitHubRawAPIErrors(updatedCtx) + require.NoError(t, err) + require.Len(t, rawErrors, 1) + + rawError := rawErrors[0] + assert.Equal(t, "failed to fetch raw content", rawError.Message) + assert.Equal(t, resp, rawError.Response) + assert.Equal(t, originalErr, rawError.Err) + }) + t.Run("multiple errors can be accumulated in context", func(t *testing.T) { // Given a context with GitHub error tracking enabled ctx := ContextWithGitHubErrors(context.Background()) @@ -82,6 +109,11 @@ func TestGitHubErrorContext(t *testing.T) { ctx, err = addGitHubGraphQLErrorToContext(ctx, gqlErr) require.NoError(t, err) + // And add a raw API error + rawErr := newGitHubRawAPIError("raw error", &http.Response{StatusCode: 404}, fmt.Errorf("not found")) + ctx, err = addRawAPIErrorToContext(ctx, rawErr) + require.NoError(t, err) + // Then we should be able to retrieve all errors apiErrors, err := GetGitHubAPIErrors(ctx) require.NoError(t, err) @@ -91,10 +123,15 @@ func TestGitHubErrorContext(t *testing.T) { require.NoError(t, err) assert.Len(t, gqlErrors, 1) + rawErrors, err := GetGitHubRawAPIErrors(ctx) + require.NoError(t, err) + assert.Len(t, rawErrors, 1) + // Verify error details assert.Equal(t, "first error", apiErrors[0].Message) assert.Equal(t, "second error", apiErrors[1].Message) assert.Equal(t, "graphql error", gqlErrors[0].Message) + assert.Equal(t, "raw error", rawErrors[0].Message) }) t.Run("context pointer sharing allows middleware to inspect errors without context propagation", func(t *testing.T) { @@ -160,6 +197,12 @@ func TestGitHubErrorContext(t *testing.T) { assert.Error(t, err) assert.Contains(t, err.Error(), "context does not contain GitHubCtxErrors") assert.Nil(t, gqlErrors) + + // Same for raw API errors + rawErrors, err := GetGitHubRawAPIErrors(ctx) + assert.Error(t, err) + assert.Contains(t, err.Error(), "context does not contain GitHubCtxErrors") + assert.Nil(t, rawErrors) }) t.Run("ContextWithGitHubErrors resets existing errors", func(t *testing.T) { @@ -169,18 +212,31 @@ func TestGitHubErrorContext(t *testing.T) { ctx, err := NewGitHubAPIErrorToCtx(ctx, "existing error", resp, fmt.Errorf("error")) require.NoError(t, err) - // Verify error exists + // Add a raw API error too + rawErr := newGitHubRawAPIError("existing raw error", &http.Response{StatusCode: 404}, fmt.Errorf("error")) + ctx, err = addRawAPIErrorToContext(ctx, rawErr) + require.NoError(t, err) + + // Verify errors exist apiErrors, err := GetGitHubAPIErrors(ctx) require.NoError(t, err) assert.Len(t, apiErrors, 1) + rawErrors, err := GetGitHubRawAPIErrors(ctx) + require.NoError(t, err) + assert.Len(t, rawErrors, 1) + // When we call ContextWithGitHubErrors again resetCtx := ContextWithGitHubErrors(ctx) - // Then the errors should be cleared + // Then all errors should be cleared apiErrors, err = GetGitHubAPIErrors(resetCtx) require.NoError(t, err) - assert.Len(t, apiErrors, 0, "Errors should be reset") + assert.Len(t, apiErrors, 0, "API errors should be reset") + + rawErrors, err = GetGitHubRawAPIErrors(resetCtx) + require.NoError(t, err) + assert.Len(t, rawErrors, 0, "Raw API errors should be reset") }) t.Run("NewGitHubAPIErrorResponse creates MCP error result and stores context error", func(t *testing.T) { diff --git a/pkg/github/__toolsnaps__/delete_project_item.snap b/pkg/github/__toolsnaps__/delete_project_item.snap index d768df10f..430c83cc8 100644 --- a/pkg/github/__toolsnaps__/delete_project_item.snap +++ b/pkg/github/__toolsnaps__/delete_project_item.snap @@ -1,5 +1,6 @@ { "annotations": { + "destructiveHint": true, "title": "Delete project item" }, "description": "Delete a specific Project item for a user or org", diff --git a/pkg/github/projects.go b/pkg/github/projects.go index 18c1f778b..0536bed99 100644 --- a/pkg/github/projects.go +++ b/pkg/github/projects.go @@ -900,8 +900,9 @@ func DeleteProjectItem(t translations.TranslationHelperFunc) inventory.ServerToo Name: "delete_project_item", Description: t("TOOL_DELETE_PROJECT_ITEM_DESCRIPTION", "Delete a specific Project item for a user or org"), Annotations: &mcp.ToolAnnotations{ - Title: t("TOOL_DELETE_PROJECT_ITEM_USER_TITLE", "Delete project item"), - ReadOnlyHint: false, + Title: t("TOOL_DELETE_PROJECT_ITEM_USER_TITLE", "Delete project item"), + ReadOnlyHint: false, + DestructiveHint: jsonschema.Ptr(true), }, InputSchema: &jsonschema.Schema{ Type: "object", diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index d8d2b27b3..c31bb7df2 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -671,6 +671,8 @@ func GetFileContents(t translations.TranslationHelperFunc) inventory.ServerTool if err != nil { return utils.NewToolResultError(err.Error()), nil, nil } + originalRef := ref + sha, err := OptionalParam[string](args, "sha") if err != nil { return utils.NewToolResultError(err.Error()), nil, nil @@ -681,7 +683,7 @@ func GetFileContents(t translations.TranslationHelperFunc) inventory.ServerTool return utils.NewToolResultError("failed to get GitHub client"), nil, nil } - rawOpts, err := resolveGitReference(ctx, client, owner, repo, ref, sha) + rawOpts, fallbackUsed, err := resolveGitReference(ctx, client, owner, repo, ref, sha) if err != nil { return utils.NewToolResultError(fmt.Sprintf("failed to resolve git reference: %s", err)), nil, nil } @@ -724,7 +726,7 @@ func GetFileContents(t translations.TranslationHelperFunc) inventory.ServerTool // If the raw content is found, return it directly body, err := io.ReadAll(resp.Body) if err != nil { - return utils.NewToolResultError("failed to read response body"), nil, nil + return ghErrors.NewGitHubRawAPIErrorResponse(ctx, "failed to get raw repository content", resp, err), nil, nil } contentType := resp.Header.Get("Content-Type") @@ -747,6 +749,12 @@ func GetFileContents(t translations.TranslationHelperFunc) inventory.ServerTool } } + // main branch ref passed in ref parameter but it doesn't exist - default branch was used + var successNote string + if fallbackUsed { + successNote = fmt.Sprintf(" Note: the provided ref '%s' does not exist, default branch '%s' was used instead.", originalRef, rawOpts.Ref) + } + // Determine if content is text or binary isTextContent := strings.HasPrefix(contentType, "text/") || contentType == "application/json" || @@ -762,9 +770,9 @@ func GetFileContents(t translations.TranslationHelperFunc) inventory.ServerTool } // Include SHA in the result metadata if fileSHA != "" { - return utils.NewToolResultResource(fmt.Sprintf("successfully downloaded text file (SHA: %s)", fileSHA), result), nil, nil + return utils.NewToolResultResource(fmt.Sprintf("successfully downloaded text file (SHA: %s)", fileSHA)+successNote, result), nil, nil } - return utils.NewToolResultResource("successfully downloaded text file", result), nil, nil + return utils.NewToolResultResource("successfully downloaded text file"+successNote, result), nil, nil } result := &mcp.ResourceContents{ @@ -774,9 +782,9 @@ func GetFileContents(t translations.TranslationHelperFunc) inventory.ServerTool } // Include SHA in the result metadata if fileSHA != "" { - return utils.NewToolResultResource(fmt.Sprintf("successfully downloaded binary file (SHA: %s)", fileSHA), result), nil, nil + return utils.NewToolResultResource(fmt.Sprintf("successfully downloaded binary file (SHA: %s)", fileSHA)+successNote, result), nil, nil } - return utils.NewToolResultResource("successfully downloaded binary file", result), nil, nil + return utils.NewToolResultResource("successfully downloaded binary file"+successNote, result), nil, nil } // Raw API call failed @@ -1833,6 +1841,20 @@ func filterPaths(entries []*github.TreeEntry, path string, maxResults int) []str return matchedPaths } +// looksLikeSHA returns true if the string appears to be a Git commit SHA. +// A SHA is a 40-character hexadecimal string. +func looksLikeSHA(s string) bool { + if len(s) != 40 { + return false + } + for _, c := range s { + if (c < '0' || c > '9') && (c < 'a' || c > 'f') && (c < 'A' || c > 'F') { + return false + } + } + return true +} + // resolveGitReference takes a user-provided ref and sha and resolves them into a // definitive commit SHA and its corresponding fully-qualified reference. // @@ -1841,8 +1863,11 @@ func filterPaths(entries []*github.TreeEntry, path string, maxResults int) []str // 1. If a specific commit `sha` is provided, it takes precedence and is used directly, // and all reference resolution is skipped. // -// 2. If no `sha` is provided, the function resolves the `ref` -// string into a fully-qualified format (e.g., "refs/heads/main") by trying +// 1a. If `sha` is empty but `ref` looks like a commit SHA (40 hexadecimal characters), +// it is returned as-is without any API calls or reference resolution. +// +// 2. If no `sha` is provided and `ref` does not look like a SHA, the function resolves +// the `ref` string into a fully-qualified format (e.g., "refs/heads/main") by trying // the following steps in order: // a). **Empty Ref:** If `ref` is empty, the repository's default branch is used. // b). **Fully-Qualified:** If `ref` already starts with "refs/", it's considered fully @@ -1859,10 +1884,15 @@ func filterPaths(entries []*github.TreeEntry, path string, maxResults int) []str // // Any unexpected (non-404) errors during the resolution process are returned // immediately. All API errors are logged with rich context to aid diagnostics. -func resolveGitReference(ctx context.Context, githubClient *github.Client, owner, repo, ref, sha string) (*raw.ContentOpts, error) { +func resolveGitReference(ctx context.Context, githubClient *github.Client, owner, repo, ref, sha string) (*raw.ContentOpts, bool, error) { // 1) If SHA explicitly provided, it's the highest priority. if sha != "" { - return &raw.ContentOpts{Ref: "", SHA: sha}, nil + return &raw.ContentOpts{Ref: "", SHA: sha}, false, nil + } + + // 1a) If sha is empty but ref looks like a SHA, return it without changes + if looksLikeSHA(ref) { + return &raw.ContentOpts{Ref: "", SHA: ref}, false, nil } originalRef := ref // Keep original ref for clearer error messages down the line. @@ -1871,16 +1901,16 @@ func resolveGitReference(ctx context.Context, githubClient *github.Client, owner var reference *github.Reference var resp *github.Response var err error + var fallbackUsed bool switch { case originalRef == "": // 2a) If ref is empty, determine the default branch. - repoInfo, resp, err := githubClient.Repositories.Get(ctx, owner, repo) + reference, err = resolveDefaultBranch(ctx, githubClient, owner, repo) if err != nil { - _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to get repository info", resp, err) - return nil, fmt.Errorf("failed to get repository info: %w", err) + return nil, false, err // Error is already wrapped in resolveDefaultBranch. } - ref = fmt.Sprintf("refs/heads/%s", repoInfo.GetDefaultBranch()) + ref = reference.GetRef() case strings.HasPrefix(originalRef, "refs/"): // 2b) Already fully qualified. The reference will be fetched at the end. case strings.HasPrefix(originalRef, "heads/") || strings.HasPrefix(originalRef, "tags/"): @@ -1905,16 +1935,27 @@ func resolveGitReference(ctx context.Context, githubClient *github.Client, owner // The tag lookup also failed. Check if it was a 404 Not Found error. ghErr2, isGhErr2 := err.(*github.ErrorResponse) if isGhErr2 && ghErr2.Response.StatusCode == http.StatusNotFound { - return nil, fmt.Errorf("could not resolve ref %q as a branch or a tag", originalRef) + if originalRef == "main" { + reference, err = resolveDefaultBranch(ctx, githubClient, owner, repo) + if err != nil { + return nil, false, err // Error is already wrapped in resolveDefaultBranch. + } + // Update ref to the actual default branch ref so the note can be generated + ref = reference.GetRef() + fallbackUsed = true + break + } + return nil, false, fmt.Errorf("could not resolve ref %q as a branch or a tag", originalRef) } + // The tag lookup failed for a different reason. _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to get reference (tag)", resp, err) - return nil, fmt.Errorf("failed to get reference for tag '%s': %w", originalRef, err) + return nil, false, fmt.Errorf("failed to get reference for tag '%s': %w", originalRef, err) } } else { // The branch lookup failed for a different reason. _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to get reference (branch)", resp, err) - return nil, fmt.Errorf("failed to get reference for branch '%s': %w", originalRef, err) + return nil, false, fmt.Errorf("failed to get reference for branch '%s': %w", originalRef, err) } } } @@ -1922,13 +1963,49 @@ func resolveGitReference(ctx context.Context, githubClient *github.Client, owner if reference == nil { reference, resp, err = githubClient.Git.GetRef(ctx, owner, repo, ref) if err != nil { - _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to get final reference", resp, err) - return nil, fmt.Errorf("failed to get final reference for %q: %w", ref, err) + if ref == "refs/heads/main" { + reference, err = resolveDefaultBranch(ctx, githubClient, owner, repo) + if err != nil { + return nil, false, err // Error is already wrapped in resolveDefaultBranch. + } + // Update ref to the actual default branch ref so the note can be generated + ref = reference.GetRef() + fallbackUsed = true + } else { + _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to get final reference", resp, err) + return nil, false, fmt.Errorf("failed to get final reference for %q: %w", ref, err) + } } } sha = reference.GetObject().GetSHA() - return &raw.ContentOpts{Ref: ref, SHA: sha}, nil + return &raw.ContentOpts{Ref: ref, SHA: sha}, fallbackUsed, nil +} + +func resolveDefaultBranch(ctx context.Context, githubClient *github.Client, owner, repo string) (*github.Reference, error) { + repoInfo, resp, err := githubClient.Repositories.Get(ctx, owner, repo) + if err != nil { + _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to get repository info", resp, err) + return nil, fmt.Errorf("failed to get repository info: %w", err) + } + + if resp != nil && resp.Body != nil { + _ = resp.Body.Close() + } + + defaultBranch := repoInfo.GetDefaultBranch() + + defaultRef, resp, err := githubClient.Git.GetRef(ctx, owner, repo, "heads/"+defaultBranch) + if err != nil { + _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to get default branch reference", resp, err) + return nil, fmt.Errorf("failed to get default branch reference: %w", err) + } + + if resp != nil && resp.Body != nil { + defer func() { _ = resp.Body.Close() }() + } + + return defaultRef, nil } // ListStarredRepositories creates a tool to list starred repositories for the authenticated user or a specified user. diff --git a/pkg/github/repositories_test.go b/pkg/github/repositories_test.go index 9d7501f35..8b5dab098 100644 --- a/pkg/github/repositories_test.go +++ b/pkg/github/repositories_test.go @@ -69,6 +69,7 @@ func Test_GetFileContents(t *testing.T) { expectedResult interface{} expectedErrMsg string expectStatus int + expectedMsg string // optional: expected message text to verify in result }{ { name: "successful text content fetch", @@ -290,6 +291,70 @@ func Test_GetFileContents(t *testing.T) { MIMEType: "text/markdown", }, }, + { + name: "successful text content fetch with note when ref falls back to default branch", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.GetReposByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"name": "repo", "default_branch": "develop"}`)) + }), + ), + mock.WithRequestMatchHandler( + mock.GetReposGitRefByOwnerByRepoByRef, + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Request for "refs/heads/main" -> 404 (doesn't exist) + // Request for "refs/heads/develop" (default branch) -> 200 + switch { + case strings.Contains(r.URL.Path, "heads/main"): + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + case strings.Contains(r.URL.Path, "heads/develop"): + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"ref": "refs/heads/develop", "object": {"sha": "abc123def456"}}`)) + default: + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + } + }), + ), + mock.WithRequestMatchHandler( + mock.GetReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + fileContent := &github.RepositoryContent{ + Name: github.Ptr("README.md"), + Path: github.Ptr("README.md"), + SHA: github.Ptr("abc123"), + Type: github.Ptr("file"), + } + contentBytes, _ := json.Marshal(fileContent) + _, _ = w.Write(contentBytes) + }), + ), + mock.WithRequestMatchHandler( + raw.GetRawReposContentsByOwnerByRepoBySHAByPath, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "text/markdown") + _, _ = w.Write(mockRawContent) + }), + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "path": "README.md", + "ref": "main", + }, + expectError: false, + expectedResult: mcp.ResourceContents{ + URI: "repo://owner/repo/abc123def456/contents/README.md", + Text: "# Test Repository\n\nThis is a test repository.", + MIMEType: "text/markdown", + }, + expectedMsg: " Note: the provided ref 'main' does not exist, default branch 'refs/heads/develop' was used instead.", + }, { name: "content fetch fails", mockedClient: mock.NewMockedHTTPClient( @@ -358,6 +423,14 @@ func Test_GetFileContents(t *testing.T) { // Handle both text and blob resources resource := getResourceResult(t, result) assert.Equal(t, expected, *resource) + + // If expectedMsg is set, verify the message text + if tc.expectedMsg != "" { + require.Len(t, result.Content, 2) + textContent, ok := result.Content[0].(*mcp.TextContent) + require.True(t, ok, "expected Content[0] to be TextContent") + assert.Contains(t, textContent.Text, tc.expectedMsg) + } case []*github.RepositoryContent: // Directory content fetch returns a text result (JSON array) textContent := getTextResult(t, result) @@ -2889,6 +2962,72 @@ func Test_GetReleaseByTag(t *testing.T) { } } +func Test_looksLikeSHA(t *testing.T) { + tests := []struct { + name string + input string + expected bool + }{ + { + name: "full 40-character SHA", + input: "abc123def456abc123def456abc123def456abc1", + expected: true, + }, + { + name: "too short", + input: "abc123def456abc123def45", + expected: false, + }, + { + name: "too long - 41 characters", + input: "abc123def456abc123def456abc123def456abc12", + expected: false, + }, + { + name: "contains invalid character - space", + input: "abc123def456abc123def456 bc123def456abc1", + expected: false, + }, + { + name: "contains invalid character - dash", + input: "abc123def456abc123d-f456abc123def456abc1", + expected: false, + }, + { + name: "contains invalid character - g", + input: "abc123def456gbc123def456abc123def456abc1", + expected: false, + }, + { + name: "branch name with slash", + input: "feature/branch", + expected: false, + }, + { + name: "empty string", + input: "", + expected: false, + }, + { + name: "all zeros SHA", + input: "0000000000000000000000000000000000000000", + expected: true, + }, + { + name: "all f's SHA", + input: "ffffffffffffffffffffffffffffffffffffffff", + expected: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := looksLikeSHA(tc.input) + assert.Equal(t, tc.expected, result) + }) + } +} + func Test_filterPaths(t *testing.T) { tests := []struct { name string @@ -3203,13 +3342,26 @@ func Test_resolveGitReference(t *testing.T) { }, expectError: false, }, + { + name: "ref looks like full SHA with empty sha parameter", + ref: "abc123def456abc123def456abc123def456abc1", + sha: "", + mockSetup: func() *http.Client { + // No API calls should be made when ref looks like SHA + return mock.NewMockedHTTPClient() + }, + expectedOutput: &raw.ContentOpts{ + SHA: "abc123def456abc123def456abc123def456abc1", + }, + expectError: false, + }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { // Setup client with mock client := github.NewClient(tc.mockSetup()) - opts, err := resolveGitReference(ctx, client, owner, repo, tc.ref, tc.sha) + opts, _, err := resolveGitReference(ctx, client, owner, repo, tc.ref, tc.sha) if tc.expectError { require.Error(t, err) diff --git a/pkg/github/tools.go b/pkg/github/tools.go index 62b67af6f..f6d4afa80 100644 --- a/pkg/github/tools.go +++ b/pkg/github/tools.go @@ -92,11 +92,6 @@ var ( Description: "GitHub Notifications related tools", Icon: "bell", } - ToolsetMetadataExperiments = inventory.ToolsetMetadata{ - ID: "experiments", - Description: "Experimental features that are not considered stable yet", - Icon: "beaker", - } ToolsetMetadataDiscussions = inventory.ToolsetMetadata{ ID: "discussions", Description: "GitHub Discussions related tools", diff --git a/pkg/octicons/octicons.go b/pkg/octicons/octicons.go index 3c56f09c0..5954a8c22 100644 --- a/pkg/octicons/octicons.go +++ b/pkg/octicons/octicons.go @@ -73,12 +73,12 @@ func Icons(name string) []mcp.Icon { { Source: DataURI(name, ThemeLight), MIMEType: "image/png", - Theme: string(ThemeLight), + Theme: mcp.IconThemeLight, }, { Source: DataURI(name, ThemeDark), MIMEType: "image/png", - Theme: string(ThemeDark), + Theme: mcp.IconThemeDark, }, } } diff --git a/pkg/octicons/octicons_test.go b/pkg/octicons/octicons_test.go index 27fc135ee..078eb744f 100644 --- a/pkg/octicons/octicons_test.go +++ b/pkg/octicons/octicons_test.go @@ -4,6 +4,7 @@ import ( "strings" "testing" + "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/stretchr/testify/assert" ) @@ -87,13 +88,13 @@ func TestIcons(t *testing.T) { assert.Equal(t, DataURI(tc.icon, ThemeLight), result[0].Source) assert.Equal(t, "image/png", result[0].MIMEType) assert.Empty(t, result[0].Sizes) // Sizes field omitted for backward compatibility - assert.Equal(t, "light", result[0].Theme) + assert.Equal(t, mcp.IconThemeLight, result[0].Theme) // Verify second icon is dark theme assert.Equal(t, DataURI(tc.icon, ThemeDark), result[1].Source) assert.Equal(t, "image/png", result[1].MIMEType) assert.Empty(t, result[1].Sizes) // Sizes field omitted for backward compatibility - assert.Equal(t, "dark", result[1].Theme) + assert.Equal(t, mcp.IconThemeDark, result[1].Theme) }) } } diff --git a/script/licenses b/script/licenses index 214efa435..5aa8ec16b 100755 --- a/script/licenses +++ b/script/licenses @@ -16,10 +16,23 @@ # # Normally these warnings are packages containing non go code, which may or may not require explicit attribution, # depending on the license. - set -e -go install github.com/google/go-licenses@latest +# Pinned version for CI reproducibility, latest for local development +# See: https://github.com/cli/cli/pull/11161 +if [ "$CI" = "true" ]; then + go install github.com/google/go-licenses@5348b744d0983d85713295ea08a20cca1654a45e # v2.0.1 +else + go install github.com/google/go-licenses@latest +fi + +# actions/setup-go does not setup the installed toolchain to be preferred over the system install, +# which causes go-licenses to raise "Package ... does not have module info" errors in CI. +# For more information, https://github.com/google/go-licenses/issues/244#issuecomment-1885098633 +if [ "$CI" = "true" ]; then + export GOROOT=$(go env GOROOT) + export PATH=${GOROOT}/bin:$PATH +fi # actions/setup-go does not setup the installed toolchain to be preferred over the system install, # which causes go-licenses to raise "Package ... does not have module info" errors in CI. diff --git a/third-party-licenses.darwin.md b/third-party-licenses.darwin.md index e44711943..5cb31cac4 100644 --- a/third-party-licenses.darwin.md +++ b/third-party-licenses.darwin.md @@ -21,7 +21,7 @@ The following packages are included for the amd64, arm64 architectures. - [github.com/google/go-github/v71/github](https://pkg.go.dev/github.com/google/go-github/v71/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v71.0.0/LICENSE)) - [github.com/google/go-github/v79/github](https://pkg.go.dev/github.com/google/go-github/v79/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v79.0.0/LICENSE)) - [github.com/google/go-querystring/query](https://pkg.go.dev/github.com/google/go-querystring/query) ([BSD-3-Clause](https://github.com/google/go-querystring/blob/v1.1.0/LICENSE)) - - [github.com/google/jsonschema-go/jsonschema](https://pkg.go.dev/github.com/google/jsonschema-go/jsonschema) ([MIT](https://github.com/google/jsonschema-go/blob/v0.3.0/LICENSE)) + - [github.com/google/jsonschema-go/jsonschema](https://pkg.go.dev/github.com/google/jsonschema-go/jsonschema) ([MIT](https://github.com/google/jsonschema-go/blob/v0.4.2/LICENSE)) - [github.com/gorilla/css/scanner](https://pkg.go.dev/github.com/gorilla/css/scanner) ([BSD-3-Clause](https://github.com/gorilla/css/blob/v1.0.1/LICENSE)) - [github.com/gorilla/mux](https://pkg.go.dev/github.com/gorilla/mux) ([BSD-3-Clause](https://github.com/gorilla/mux/blob/v1.8.0/LICENSE)) - [github.com/josephburnett/jd/v2](https://pkg.go.dev/github.com/josephburnett/jd/v2) ([MIT](https://github.com/josephburnett/jd/blob/v1.9.2/LICENSE)) @@ -29,7 +29,7 @@ The following packages are included for the amd64, arm64 architectures. - [github.com/mailru/easyjson](https://pkg.go.dev/github.com/mailru/easyjson) ([MIT](https://github.com/mailru/easyjson/blob/v0.7.7/LICENSE)) - [github.com/microcosm-cc/bluemonday](https://pkg.go.dev/github.com/microcosm-cc/bluemonday) ([BSD-3-Clause](https://github.com/microcosm-cc/bluemonday/blob/v1.0.27/LICENSE.md)) - [github.com/migueleliasweb/go-github-mock/src/mock](https://pkg.go.dev/github.com/migueleliasweb/go-github-mock/src/mock) ([MIT](https://github.com/migueleliasweb/go-github-mock/blob/v1.3.0/LICENSE)) - - [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.2.0-pre.1/LICENSE)) + - [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.2.0/LICENSE)) - [github.com/muesli/cache2go](https://pkg.go.dev/github.com/muesli/cache2go) ([BSD-3-Clause](https://github.com/muesli/cache2go/blob/518229cd8021/LICENSE.txt)) - [github.com/pelletier/go-toml/v2](https://pkg.go.dev/github.com/pelletier/go-toml/v2) ([MIT](https://github.com/pelletier/go-toml/blob/v2.2.4/LICENSE)) - [github.com/sagikazarmark/locafero](https://pkg.go.dev/github.com/sagikazarmark/locafero) ([MIT](https://github.com/sagikazarmark/locafero/blob/v0.11.0/LICENSE)) @@ -38,7 +38,7 @@ The following packages are included for the amd64, arm64 architectures. - [github.com/sourcegraph/conc](https://pkg.go.dev/github.com/sourcegraph/conc) ([MIT](https://github.com/sourcegraph/conc/blob/5f936abd7ae8/LICENSE)) - [github.com/spf13/afero](https://pkg.go.dev/github.com/spf13/afero) ([Apache-2.0](https://github.com/spf13/afero/blob/v1.15.0/LICENSE.txt)) - [github.com/spf13/cast](https://pkg.go.dev/github.com/spf13/cast) ([MIT](https://github.com/spf13/cast/blob/v1.10.0/LICENSE)) - - [github.com/spf13/cobra](https://pkg.go.dev/github.com/spf13/cobra) ([Apache-2.0](https://github.com/spf13/cobra/blob/v1.10.1/LICENSE.txt)) + - [github.com/spf13/cobra](https://pkg.go.dev/github.com/spf13/cobra) ([Apache-2.0](https://github.com/spf13/cobra/blob/v1.10.2/LICENSE.txt)) - [github.com/spf13/pflag](https://pkg.go.dev/github.com/spf13/pflag) ([BSD-3-Clause](https://github.com/spf13/pflag/blob/v1.0.10/LICENSE)) - [github.com/spf13/viper](https://pkg.go.dev/github.com/spf13/viper) ([MIT](https://github.com/spf13/viper/blob/v1.21.0/LICENSE)) - [github.com/subosito/gotenv](https://pkg.go.dev/github.com/subosito/gotenv) ([MIT](https://github.com/subosito/gotenv/blob/v1.6.0/LICENSE)) diff --git a/third-party-licenses.linux.md b/third-party-licenses.linux.md index f5c147d59..8d0829a63 100644 --- a/third-party-licenses.linux.md +++ b/third-party-licenses.linux.md @@ -21,7 +21,7 @@ The following packages are included for the 386, amd64, arm64 architectures. - [github.com/google/go-github/v71/github](https://pkg.go.dev/github.com/google/go-github/v71/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v71.0.0/LICENSE)) - [github.com/google/go-github/v79/github](https://pkg.go.dev/github.com/google/go-github/v79/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v79.0.0/LICENSE)) - [github.com/google/go-querystring/query](https://pkg.go.dev/github.com/google/go-querystring/query) ([BSD-3-Clause](https://github.com/google/go-querystring/blob/v1.1.0/LICENSE)) - - [github.com/google/jsonschema-go/jsonschema](https://pkg.go.dev/github.com/google/jsonschema-go/jsonschema) ([MIT](https://github.com/google/jsonschema-go/blob/v0.3.0/LICENSE)) + - [github.com/google/jsonschema-go/jsonschema](https://pkg.go.dev/github.com/google/jsonschema-go/jsonschema) ([MIT](https://github.com/google/jsonschema-go/blob/v0.4.2/LICENSE)) - [github.com/gorilla/css/scanner](https://pkg.go.dev/github.com/gorilla/css/scanner) ([BSD-3-Clause](https://github.com/gorilla/css/blob/v1.0.1/LICENSE)) - [github.com/gorilla/mux](https://pkg.go.dev/github.com/gorilla/mux) ([BSD-3-Clause](https://github.com/gorilla/mux/blob/v1.8.0/LICENSE)) - [github.com/josephburnett/jd/v2](https://pkg.go.dev/github.com/josephburnett/jd/v2) ([MIT](https://github.com/josephburnett/jd/blob/v1.9.2/LICENSE)) @@ -29,7 +29,7 @@ The following packages are included for the 386, amd64, arm64 architectures. - [github.com/mailru/easyjson](https://pkg.go.dev/github.com/mailru/easyjson) ([MIT](https://github.com/mailru/easyjson/blob/v0.7.7/LICENSE)) - [github.com/microcosm-cc/bluemonday](https://pkg.go.dev/github.com/microcosm-cc/bluemonday) ([BSD-3-Clause](https://github.com/microcosm-cc/bluemonday/blob/v1.0.27/LICENSE.md)) - [github.com/migueleliasweb/go-github-mock/src/mock](https://pkg.go.dev/github.com/migueleliasweb/go-github-mock/src/mock) ([MIT](https://github.com/migueleliasweb/go-github-mock/blob/v1.3.0/LICENSE)) - - [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.2.0-pre.1/LICENSE)) + - [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.2.0/LICENSE)) - [github.com/muesli/cache2go](https://pkg.go.dev/github.com/muesli/cache2go) ([BSD-3-Clause](https://github.com/muesli/cache2go/blob/518229cd8021/LICENSE.txt)) - [github.com/pelletier/go-toml/v2](https://pkg.go.dev/github.com/pelletier/go-toml/v2) ([MIT](https://github.com/pelletier/go-toml/blob/v2.2.4/LICENSE)) - [github.com/sagikazarmark/locafero](https://pkg.go.dev/github.com/sagikazarmark/locafero) ([MIT](https://github.com/sagikazarmark/locafero/blob/v0.11.0/LICENSE)) @@ -38,7 +38,7 @@ The following packages are included for the 386, amd64, arm64 architectures. - [github.com/sourcegraph/conc](https://pkg.go.dev/github.com/sourcegraph/conc) ([MIT](https://github.com/sourcegraph/conc/blob/5f936abd7ae8/LICENSE)) - [github.com/spf13/afero](https://pkg.go.dev/github.com/spf13/afero) ([Apache-2.0](https://github.com/spf13/afero/blob/v1.15.0/LICENSE.txt)) - [github.com/spf13/cast](https://pkg.go.dev/github.com/spf13/cast) ([MIT](https://github.com/spf13/cast/blob/v1.10.0/LICENSE)) - - [github.com/spf13/cobra](https://pkg.go.dev/github.com/spf13/cobra) ([Apache-2.0](https://github.com/spf13/cobra/blob/v1.10.1/LICENSE.txt)) + - [github.com/spf13/cobra](https://pkg.go.dev/github.com/spf13/cobra) ([Apache-2.0](https://github.com/spf13/cobra/blob/v1.10.2/LICENSE.txt)) - [github.com/spf13/pflag](https://pkg.go.dev/github.com/spf13/pflag) ([BSD-3-Clause](https://github.com/spf13/pflag/blob/v1.0.10/LICENSE)) - [github.com/spf13/viper](https://pkg.go.dev/github.com/spf13/viper) ([MIT](https://github.com/spf13/viper/blob/v1.21.0/LICENSE)) - [github.com/subosito/gotenv](https://pkg.go.dev/github.com/subosito/gotenv) ([MIT](https://github.com/subosito/gotenv/blob/v1.6.0/LICENSE)) diff --git a/third-party-licenses.windows.md b/third-party-licenses.windows.md index b3de372e7..79b925138 100644 --- a/third-party-licenses.windows.md +++ b/third-party-licenses.windows.md @@ -21,7 +21,7 @@ The following packages are included for the 386, amd64, arm64 architectures. - [github.com/google/go-github/v71/github](https://pkg.go.dev/github.com/google/go-github/v71/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v71.0.0/LICENSE)) - [github.com/google/go-github/v79/github](https://pkg.go.dev/github.com/google/go-github/v79/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v79.0.0/LICENSE)) - [github.com/google/go-querystring/query](https://pkg.go.dev/github.com/google/go-querystring/query) ([BSD-3-Clause](https://github.com/google/go-querystring/blob/v1.1.0/LICENSE)) - - [github.com/google/jsonschema-go/jsonschema](https://pkg.go.dev/github.com/google/jsonschema-go/jsonschema) ([MIT](https://github.com/google/jsonschema-go/blob/v0.3.0/LICENSE)) + - [github.com/google/jsonschema-go/jsonschema](https://pkg.go.dev/github.com/google/jsonschema-go/jsonschema) ([MIT](https://github.com/google/jsonschema-go/blob/v0.4.2/LICENSE)) - [github.com/gorilla/css/scanner](https://pkg.go.dev/github.com/gorilla/css/scanner) ([BSD-3-Clause](https://github.com/gorilla/css/blob/v1.0.1/LICENSE)) - [github.com/gorilla/mux](https://pkg.go.dev/github.com/gorilla/mux) ([BSD-3-Clause](https://github.com/gorilla/mux/blob/v1.8.0/LICENSE)) - [github.com/inconshreveable/mousetrap](https://pkg.go.dev/github.com/inconshreveable/mousetrap) ([Apache-2.0](https://github.com/inconshreveable/mousetrap/blob/v1.1.0/LICENSE)) @@ -30,7 +30,7 @@ The following packages are included for the 386, amd64, arm64 architectures. - [github.com/mailru/easyjson](https://pkg.go.dev/github.com/mailru/easyjson) ([MIT](https://github.com/mailru/easyjson/blob/v0.7.7/LICENSE)) - [github.com/microcosm-cc/bluemonday](https://pkg.go.dev/github.com/microcosm-cc/bluemonday) ([BSD-3-Clause](https://github.com/microcosm-cc/bluemonday/blob/v1.0.27/LICENSE.md)) - [github.com/migueleliasweb/go-github-mock/src/mock](https://pkg.go.dev/github.com/migueleliasweb/go-github-mock/src/mock) ([MIT](https://github.com/migueleliasweb/go-github-mock/blob/v1.3.0/LICENSE)) - - [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.2.0-pre.1/LICENSE)) + - [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.2.0/LICENSE)) - [github.com/muesli/cache2go](https://pkg.go.dev/github.com/muesli/cache2go) ([BSD-3-Clause](https://github.com/muesli/cache2go/blob/518229cd8021/LICENSE.txt)) - [github.com/pelletier/go-toml/v2](https://pkg.go.dev/github.com/pelletier/go-toml/v2) ([MIT](https://github.com/pelletier/go-toml/blob/v2.2.4/LICENSE)) - [github.com/sagikazarmark/locafero](https://pkg.go.dev/github.com/sagikazarmark/locafero) ([MIT](https://github.com/sagikazarmark/locafero/blob/v0.11.0/LICENSE)) @@ -39,7 +39,7 @@ The following packages are included for the 386, amd64, arm64 architectures. - [github.com/sourcegraph/conc](https://pkg.go.dev/github.com/sourcegraph/conc) ([MIT](https://github.com/sourcegraph/conc/blob/5f936abd7ae8/LICENSE)) - [github.com/spf13/afero](https://pkg.go.dev/github.com/spf13/afero) ([Apache-2.0](https://github.com/spf13/afero/blob/v1.15.0/LICENSE.txt)) - [github.com/spf13/cast](https://pkg.go.dev/github.com/spf13/cast) ([MIT](https://github.com/spf13/cast/blob/v1.10.0/LICENSE)) - - [github.com/spf13/cobra](https://pkg.go.dev/github.com/spf13/cobra) ([Apache-2.0](https://github.com/spf13/cobra/blob/v1.10.1/LICENSE.txt)) + - [github.com/spf13/cobra](https://pkg.go.dev/github.com/spf13/cobra) ([Apache-2.0](https://github.com/spf13/cobra/blob/v1.10.2/LICENSE.txt)) - [github.com/spf13/pflag](https://pkg.go.dev/github.com/spf13/pflag) ([BSD-3-Clause](https://github.com/spf13/pflag/blob/v1.0.10/LICENSE)) - [github.com/spf13/viper](https://pkg.go.dev/github.com/spf13/viper) ([MIT](https://github.com/spf13/viper/blob/v1.21.0/LICENSE)) - [github.com/subosito/gotenv](https://pkg.go.dev/github.com/subosito/gotenv) ([MIT](https://github.com/subosito/gotenv/blob/v1.6.0/LICENSE))