Skip to content

Commit 9209f5c

Browse files
authored
Merge branch 'main' into feat/259/assign-reviewers
2 parents 7676eae + 7c62774 commit 9209f5c

File tree

17 files changed

+186
-120
lines changed

17 files changed

+186
-120
lines changed

.github/workflows/go.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Unit Tests
1+
name: Build and Test Go Project
22
on: [push, pull_request]
33

44
permissions:

.github/workflows/lint.yaml

Lines changed: 0 additions & 45 deletions
This file was deleted.

.github/workflows/lint.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ __debug_bin*
1111

1212
# Go
1313
vendor
14+
bin/

.golangci.yml

Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,37 @@
1-
# https://golangci-lint.run/usage/configuration
21
version: "2"
3-
42
run:
5-
timeout: 5m
6-
tests: true
73
concurrency: 4
8-
4+
tests: true
95
linters:
106
enable:
11-
- govet
12-
- errcheck
13-
- staticcheck
14-
- revive
15-
- ineffassign
16-
- unused
17-
- misspell
18-
- nakedret
197
- bodyclose
208
- gocritic
21-
- makezero
229
- 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$
2325
settings:
2426
staticcheck:
2527
checks:
26-
- all
27-
- '-QF1008' # Allow embedded structs to be referenced by field
28-
- '-ST1000' # Do not require package comments
29-
revive:
30-
rules:
31-
- name: exported
32-
disabled: true
33-
- name: exported
34-
disabled: true
35-
- name: package-comments
36-
disabled: true
37-
28+
- "all"
29+
- -QF1008
30+
- -ST1000
3831
formatters:
39-
enable:
40-
- gofmt
41-
- goimports
42-
43-
output:
44-
formats:
45-
text:
46-
print-linter-name: true
47-
print-issued-lines: true
32+
exclusions:
33+
generated: lax
34+
paths:
35+
- third_party$
36+
- builtin$
37+
- examples$

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,7 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description
456456
- `repo`: Repository name (string, required)
457457
- `return_content`: Returns actual log content instead of URLs (boolean, optional)
458458
- `run_id`: Workflow run ID (required when using failed_only) (number, optional)
459+
- `tail_lines`: Number of lines to return from the end of the log (number, optional)
459460

460461
- **get_workflow_run** - Get workflow run
461462
- `owner`: Repository owner (string, required)

cmd/github-mcp-server/generate_docs.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ var generateDocsCmd = &cobra.Command{
2323
Use: "generate-docs",
2424
Short: "Generate documentation for tools and toolsets",
2525
Long: `Generate the automated sections of README.md and docs/remote-server.md with current tool and toolset information.`,
26-
RunE: func(cmd *cobra.Command, args []string) error {
26+
RunE: func(_ *cobra.Command, _ []string) error {
2727
return generateAllDocs()
2828
},
2929
}
@@ -33,17 +33,17 @@ func init() {
3333
}
3434

3535
// mockGetClient returns a mock GitHub client for documentation generation
36-
func mockGetClient(ctx context.Context) (*gogithub.Client, error) {
36+
func mockGetClient(_ context.Context) (*gogithub.Client, error) {
3737
return gogithub.NewClient(nil), nil
3838
}
3939

4040
// mockGetGQLClient returns a mock GraphQL client for documentation generation
41-
func mockGetGQLClient(ctx context.Context) (*githubv4.Client, error) {
41+
func mockGetGQLClient(_ context.Context) (*githubv4.Client, error) {
4242
return githubv4.NewClient(nil), nil
4343
}
4444

4545
// mockGetRawClient returns a mock raw client for documentation generation
46-
func mockGetRawClient(ctx context.Context) (*raw.Client, error) {
46+
func mockGetRawClient(_ context.Context) (*raw.Client, error) {
4747
return nil, nil
4848
}
4949

cmd/github-mcp-server/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ func main() {
103103
}
104104
}
105105

106-
func wordSepNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName {
106+
func wordSepNormalizeFunc(_ *pflag.FlagSet, name string) pflag.NormalizedName {
107107
from := []string{"_"}
108108
to := "-"
109109
for _, sep := range from {

pkg/errors/error_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ func TestGitHubErrorContext(t *testing.T) {
260260

261261
t.Run("NewGitHubAPIErrorToCtx with nil context does not error", func(t *testing.T) {
262262
// Given a nil context
263-
var ctx context.Context = nil
263+
var ctx context.Context
264264

265265
// Create a mock GitHub response
266266
resp := &github.Response{

pkg/github/actions.go

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,10 @@ func GetJobLogs(getClient GetClientFn, t translations.TranslationHelperFunc) (to
584584
mcp.WithBoolean("return_content",
585585
mcp.Description("Returns actual log content instead of URLs"),
586586
),
587+
mcp.WithNumber("tail_lines",
588+
mcp.Description("Number of lines to return from the end of the log"),
589+
mcp.DefaultNumber(500),
590+
),
587591
),
588592
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
589593
owner, err := RequiredParam[string](request, "owner")
@@ -612,6 +616,14 @@ func GetJobLogs(getClient GetClientFn, t translations.TranslationHelperFunc) (to
612616
if err != nil {
613617
return mcp.NewToolResultError(err.Error()), nil
614618
}
619+
tailLines, err := OptionalIntParam(request, "tail_lines")
620+
if err != nil {
621+
return mcp.NewToolResultError(err.Error()), nil
622+
}
623+
// Default to 500 lines if not specified
624+
if tailLines == 0 {
625+
tailLines = 500
626+
}
615627

616628
client, err := getClient(ctx)
617629
if err != nil {
@@ -628,18 +640,18 @@ func GetJobLogs(getClient GetClientFn, t translations.TranslationHelperFunc) (to
628640

629641
if failedOnly && runID > 0 {
630642
// Handle failed-only mode: get logs for all failed jobs in the workflow run
631-
return handleFailedJobLogs(ctx, client, owner, repo, int64(runID), returnContent)
643+
return handleFailedJobLogs(ctx, client, owner, repo, int64(runID), returnContent, tailLines)
632644
} else if jobID > 0 {
633645
// Handle single job mode
634-
return handleSingleJobLogs(ctx, client, owner, repo, int64(jobID), returnContent)
646+
return handleSingleJobLogs(ctx, client, owner, repo, int64(jobID), returnContent, tailLines)
635647
}
636648

637649
return mcp.NewToolResultError("Either job_id must be provided for single job logs, or run_id with failed_only=true for failed job logs"), nil
638650
}
639651
}
640652

641653
// handleFailedJobLogs gets logs for all failed jobs in a workflow run
642-
func handleFailedJobLogs(ctx context.Context, client *github.Client, owner, repo string, runID int64, returnContent bool) (*mcp.CallToolResult, error) {
654+
func handleFailedJobLogs(ctx context.Context, client *github.Client, owner, repo string, runID int64, returnContent bool, tailLines int) (*mcp.CallToolResult, error) {
643655
// First, get all jobs for the workflow run
644656
jobs, resp, err := client.Actions.ListWorkflowJobs(ctx, owner, repo, runID, &github.ListWorkflowJobsOptions{
645657
Filter: "latest",
@@ -671,7 +683,7 @@ func handleFailedJobLogs(ctx context.Context, client *github.Client, owner, repo
671683
// Collect logs for all failed jobs
672684
var logResults []map[string]any
673685
for _, job := range failedJobs {
674-
jobResult, resp, err := getJobLogData(ctx, client, owner, repo, job.GetID(), job.GetName(), returnContent)
686+
jobResult, resp, err := getJobLogData(ctx, client, owner, repo, job.GetID(), job.GetName(), returnContent, tailLines)
675687
if err != nil {
676688
// Continue with other jobs even if one fails
677689
jobResult = map[string]any{
@@ -704,8 +716,8 @@ func handleFailedJobLogs(ctx context.Context, client *github.Client, owner, repo
704716
}
705717

706718
// handleSingleJobLogs gets logs for a single job
707-
func handleSingleJobLogs(ctx context.Context, client *github.Client, owner, repo string, jobID int64, returnContent bool) (*mcp.CallToolResult, error) {
708-
jobResult, resp, err := getJobLogData(ctx, client, owner, repo, jobID, "", returnContent)
719+
func handleSingleJobLogs(ctx context.Context, client *github.Client, owner, repo string, jobID int64, returnContent bool, tailLines int) (*mcp.CallToolResult, error) {
720+
jobResult, resp, err := getJobLogData(ctx, client, owner, repo, jobID, "", returnContent, tailLines)
709721
if err != nil {
710722
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to get job logs", resp, err), nil
711723
}
@@ -719,7 +731,7 @@ func handleSingleJobLogs(ctx context.Context, client *github.Client, owner, repo
719731
}
720732

721733
// getJobLogData retrieves log data for a single job, either as URL or content
722-
func getJobLogData(ctx context.Context, client *github.Client, owner, repo string, jobID int64, jobName string, returnContent bool) (map[string]any, *github.Response, error) {
734+
func getJobLogData(ctx context.Context, client *github.Client, owner, repo string, jobID int64, jobName string, returnContent bool, tailLines int) (map[string]any, *github.Response, error) {
723735
// Get the download URL for the job logs
724736
url, resp, err := client.Actions.GetWorkflowJobLogs(ctx, owner, repo, jobID, 1)
725737
if err != nil {
@@ -736,7 +748,7 @@ func getJobLogData(ctx context.Context, client *github.Client, owner, repo strin
736748

737749
if returnContent {
738750
// Download and return the actual log content
739-
content, httpResp, err := downloadLogContent(url.String()) //nolint:bodyclose // Response body is closed in downloadLogContent, but we need to return httpResp
751+
content, originalLength, httpResp, err := downloadLogContent(url.String(), tailLines) //nolint:bodyclose // Response body is closed in downloadLogContent, but we need to return httpResp
740752
if err != nil {
741753
// To keep the return value consistent wrap the response as a GitHub Response
742754
ghRes := &github.Response{
@@ -746,6 +758,7 @@ func getJobLogData(ctx context.Context, client *github.Client, owner, repo strin
746758
}
747759
result["logs_content"] = content
748760
result["message"] = "Job logs content retrieved successfully"
761+
result["original_length"] = originalLength
749762
} else {
750763
// Return just the URL
751764
result["logs_url"] = url.String()
@@ -757,25 +770,46 @@ func getJobLogData(ctx context.Context, client *github.Client, owner, repo strin
757770
}
758771

759772
// downloadLogContent downloads the actual log content from a GitHub logs URL
760-
func downloadLogContent(logURL string) (string, *http.Response, error) {
773+
func downloadLogContent(logURL string, tailLines int) (string, int, *http.Response, error) {
761774
httpResp, err := http.Get(logURL) //nolint:gosec // URLs are provided by GitHub API and are safe
762775
if err != nil {
763-
return "", httpResp, fmt.Errorf("failed to download logs: %w", err)
776+
return "", 0, httpResp, fmt.Errorf("failed to download logs: %w", err)
764777
}
765778
defer func() { _ = httpResp.Body.Close() }()
766779

767780
if httpResp.StatusCode != http.StatusOK {
768-
return "", httpResp, fmt.Errorf("failed to download logs: HTTP %d", httpResp.StatusCode)
781+
return "", 0, httpResp, fmt.Errorf("failed to download logs: HTTP %d", httpResp.StatusCode)
769782
}
770783

771784
content, err := io.ReadAll(httpResp.Body)
772785
if err != nil {
773-
return "", httpResp, fmt.Errorf("failed to read log content: %w", err)
786+
return "", 0, httpResp, fmt.Errorf("failed to read log content: %w", err)
774787
}
775788

776789
// Clean up and format the log content for better readability
777790
logContent := strings.TrimSpace(string(content))
778-
return logContent, httpResp, nil
791+
792+
trimmedContent, lineCount := trimContent(logContent, tailLines)
793+
return trimmedContent, lineCount, httpResp, nil
794+
}
795+
796+
// trimContent trims the content to a maximum length and returns the trimmed content and an original length
797+
func trimContent(content string, tailLines int) (string, int) {
798+
// Truncate to tail_lines if specified
799+
lineCount := 0
800+
if tailLines > 0 {
801+
802+
// Count backwards to find the nth newline from the end
803+
for i := len(content) - 1; i >= 0 && lineCount < tailLines; i-- {
804+
if content[i] == '\n' {
805+
lineCount++
806+
if lineCount == tailLines {
807+
content = content[i+1:]
808+
}
809+
}
810+
}
811+
}
812+
return content, lineCount
779813
}
780814

781815
// RerunWorkflowRun creates a tool to re-run an entire workflow run

0 commit comments

Comments
 (0)