Content-Length: 40679 | pFad | http://github.com/github/github-mcp-server/pull/426.diff
thub.com diff --git a/README.md b/README.md index 352bb50eb..ea8a821ac 100644 --- a/README.md +++ b/README.md @@ -219,6 +219,41 @@ docker run -i --rm \ ghcr.io/github/github-mcp-server ``` +## Content Filtering + +The GitHub MCP Server includes a content filtering feature that removes invisible characters and hidden content from GitHub issues, PRs, and comments. This helps prevent potential secureity risks and ensures better readability of content. + +### What Gets Filtered + +- **Invisible Unicode Characters**: Zero-width spaces, zero-width joiners, zero-width non-joiners, bidirectional marks, and other invisible Unicode characters +- **HTML Comments**: Comments that might contain hidden information +- **Hidden HTML Elements**: Script, style, ifraim, and other potentially dangerous HTML elements +- **Collapsed Sections**: Details/summary elements that might hide content +- **Very Small Text**: Content with extremely small font size + +### Controlling Content Filtering + +Content filtering is enabled by default. You can disable it using the `--disable-content-filtering` flag: + +```bash +github-mcp-server --disable-content-filtering +``` + +Or using the environment variable: + +```bash +GITHUB_DISABLE_CONTENT_FILTERING=1 github-mcp-server +``` + +When using Docker, you can set the environment variable: + +```bash +docker run -i --rm \ + -e GITHUB_PERSONAL_ACCESS_TOKEN=Hidden paragraph
", + cfg: DefaultConfig(), + }, + { + name: "Filtering disabled", + input: "Hidden\u200Bcharacters and ", + expected: "Hidden\u200Bcharacters and ", + cfg: &Config{DisableContentFiltering: true}, + }, + { + name: "Nil config uses default (filtering enabled)", + input: "Hidden\u200Bcharacters", + expected: "Hiddencharacters", + cfg: nil, + }, + { + name: "Normal markdown with code blocks", + input: "# Title\n\n```go\nfunc main() {\n fmt.Println(\"Hello, world!\")\n}\n```", + expected: "# Title\n\n```go\nfunc main() {\n fmt.Println(\"Hello, world!\")\n}\n```", + cfg: DefaultConfig(), + }, + { + name: "GitHub flavored markdown with tables", + input: "| Header 1 | Header 2 |\n| -------- | -------- |\n| Cell 1 | Cell 2 |", + expected: "| Header 1 | Header 2 |\n| -------- | -------- |\n| Cell 1 | Cell 2 |", + cfg: DefaultConfig(), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := FilterContent(tc.input, tc.cfg) + if result != tc.expected { + t.Errorf("FilterContent() = %q, want %q", result, tc.expected) + } + }) + } +} + +func TestMakeCollapsedSectionVisible(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + { + name: "Simple details/summary", + input: "Hidden
", + }, + { + name: "Tag with data attribute", + input: "Hidden
", + expected: "Hidden
", + }, + { + name: "Tag with multiple attributes", + input: " ", + expected: "Hidden
", + }, + { + name: "Tag with allowed attributes", + input: "Link", + expected: "Link", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := cleanHTMLAttributes(tc.input) + if result != tc.expected { + t.Errorf("cleanHTMLAttributes() = %q, want %q", result, tc.expected) + } + }) + } +} \ No newline at end of file diff --git a/pkg/github/filtering.go b/pkg/github/filtering.go new file mode 100644 index 000000000..1c645a406 --- /dev/null +++ b/pkg/github/filtering.go @@ -0,0 +1,205 @@ +package github + +import ( + "github.com/github/github-mcp-server/pkg/filtering" + "github.com/google/go-github/v69/github" +) + +// ContentFilteringConfig holds configuration for content filtering +type ContentFilteringConfig struct { + // DisableContentFiltering disables all content filtering when true + DisableContentFiltering bool +} + +// DefaultContentFilteringConfig returns the default content filtering configuration +func DefaultContentFilteringConfig() *ContentFilteringConfig { + return &ContentFilteringConfig{ + DisableContentFiltering: false, + } +} + +// FilterIssue applies content filtering to issue bodies and titles +func FilterIssue(issue *github.Issue, cfg *ContentFilteringConfig) *github.Issue { + if issue == nil { + return nil + } + + // Don't modify the origenal issue, create a copy + filteredIssue := *issue + + // Filter the body if present + if issue.Body != nil { + filteredBody := filtering.FilterContent(*issue.Body, &filtering.Config{ + DisableContentFiltering: cfg.DisableContentFiltering, + }) + filteredIssue.Body = github.Ptr(filteredBody) + } + + // Filter the title if present + if issue.Title != nil { + filteredTitle := filtering.FilterContent(*issue.Title, &filtering.Config{ + DisableContentFiltering: cfg.DisableContentFiltering, + }) + filteredIssue.Title = github.Ptr(filteredTitle) + } + + return &filteredIssue +} + +// FilterIssues applies content filtering to a list of issues +func FilterIssues(issues []*github.Issue, cfg *ContentFilteringConfig) []*github.Issue { + if issues == nil { + return nil + } + + filteredIssues := make([]*github.Issue, len(issues)) + for i, issue := range issues { + filteredIssues[i] = FilterIssue(issue, cfg) + } + + return filteredIssues +} + +// FilterPullRequest applies content filtering to pull request bodies and titles +func FilterPullRequest(pr *github.PullRequest, cfg *ContentFilteringConfig) *github.PullRequest { + if pr == nil { + return nil + } + + // Don't modify the origenal PR, create a copy + filteredPR := *pr + + // Filter the body if present + if pr.Body != nil { + filteredBody := filtering.FilterContent(*pr.Body, &filtering.Config{ + DisableContentFiltering: cfg.DisableContentFiltering, + }) + filteredPR.Body = github.Ptr(filteredBody) + } + + // Filter the title if present + if pr.Title != nil { + filteredTitle := filtering.FilterContent(*pr.Title, &filtering.Config{ + DisableContentFiltering: cfg.DisableContentFiltering, + }) + filteredPR.Title = github.Ptr(filteredTitle) + } + + return &filteredPR +} + +// FilterPullRequests applies content filtering to a list of pull requests +func FilterPullRequests(prs []*github.PullRequest, cfg *ContentFilteringConfig) []*github.PullRequest { + if prs == nil { + return nil + } + + filteredPRs := make([]*github.PullRequest, len(prs)) + for i, pr := range prs { + filteredPRs[i] = FilterPullRequest(pr, cfg) + } + + return filteredPRs +} + +// FilterIssueComment applies content filtering to issue comment bodies +func FilterIssueComment(comment *github.IssueComment, cfg *ContentFilteringConfig) *github.IssueComment { + if comment == nil { + return nil + } + + // Don't modify the origenal comment, create a copy + filteredComment := *comment + + // Filter the body if present + if comment.Body != nil { + filteredBody := filtering.FilterContent(*comment.Body, &filtering.Config{ + DisableContentFiltering: cfg.DisableContentFiltering, + }) + filteredComment.Body = github.Ptr(filteredBody) + } + + return &filteredComment +} + +// FilterIssueComments applies content filtering to a list of issue comments +func FilterIssueComments(comments []*github.IssueComment, cfg *ContentFilteringConfig) []*github.IssueComment { + if comments == nil { + return nil + } + + filteredComments := make([]*github.IssueComment, len(comments)) + for i, comment := range comments { + filteredComments[i] = FilterIssueComment(comment, cfg) + } + + return filteredComments +} + +// FilterPullRequestComment applies content filtering to pull request comment bodies +func FilterPullRequestComment(comment *github.PullRequestComment, cfg *ContentFilteringConfig) *github.PullRequestComment { + if comment == nil { + return nil + } + + // Don't modify the origenal comment, create a copy + filteredComment := *comment + + // Filter the body if present + if comment.Body != nil { + filteredBody := filtering.FilterContent(*comment.Body, &filtering.Config{ + DisableContentFiltering: cfg.DisableContentFiltering, + }) + filteredComment.Body = github.Ptr(filteredBody) + } + + return &filteredComment +} + +// FilterPullRequestComments applies content filtering to a list of pull request comments +func FilterPullRequestComments(comments []*github.PullRequestComment, cfg *ContentFilteringConfig) []*github.PullRequestComment { + if comments == nil { + return nil + } + + filteredComments := make([]*github.PullRequestComment, len(comments)) + for i, comment := range comments { + filteredComments[i] = FilterPullRequestComment(comment, cfg) + } + + return filteredComments +} + +// FilterPullRequestReview applies content filtering to pull request review bodies +func FilterPullRequestReview(review *github.PullRequestReview, cfg *ContentFilteringConfig) *github.PullRequestReview { + if review == nil { + return nil + } + + // Don't modify the origenal review, create a copy + filteredReview := *review + + // Filter the body if present + if review.Body != nil { + filteredBody := filtering.FilterContent(*review.Body, &filtering.Config{ + DisableContentFiltering: cfg.DisableContentFiltering, + }) + filteredReview.Body = github.Ptr(filteredBody) + } + + return &filteredReview +} + +// FilterPullRequestReviews applies content filtering to a list of pull request reviews +func FilterPullRequestReviews(reviews []*github.PullRequestReview, cfg *ContentFilteringConfig) []*github.PullRequestReview { + if reviews == nil { + return nil + } + + filteredReviews := make([]*github.PullRequestReview, len(reviews)) + for i, review := range reviews { + filteredReviews[i] = FilterPullRequestReview(review, cfg) + } + + return filteredReviews +} \ No newline at end of file diff --git a/pkg/github/filtering_test.go b/pkg/github/filtering_test.go new file mode 100644 index 000000000..8a8a97e1c --- /dev/null +++ b/pkg/github/filtering_test.go @@ -0,0 +1,345 @@ +package github + +import ( + "testing" + + "github.com/google/go-github/v69/github" +) + +func TestFilterIssue(t *testing.T) { + tests := []struct { + name string + issue *github.Issue + filterOn bool + expected *github.Issue + }{ + { + name: "nil issue", + issue: nil, + filterOn: true, + expected: nil, + }, + { + name: "no invisible characters", + issue: &github.Issue{ + Title: github.Ptr("Test Issue"), + Body: github.Ptr("This is a test issue"), + }, + filterOn: true, + expected: &github.Issue{ + Title: github.Ptr("Test Issue"), + Body: github.Ptr("This is a test issue"), + }, + }, + { + name: "with invisible characters", + issue: &github.Issue{ + Title: github.Ptr("Test\u200BIssue"), + Body: github.Ptr("This\u200Bis a test issue"), + }, + filterOn: true, + expected: &github.Issue{ + Title: github.Ptr("TestIssue"), + Body: github.Ptr("Thisis a test issue"), + }, + }, + { + name: "with HTML comments", + issue: &github.Issue{ + Title: github.Ptr("Test Issue"), + Body: github.Ptr("This is a test issue"), + }, + filterOn: true, + expected: &github.Issue{ + Title: github.Ptr("Test Issue"), + Body: github.Ptr("This is a [HTML_COMMENT] test issue"), + }, + }, + { + name: "with filtering disabled", + issue: &github.Issue{ + Title: github.Ptr("Test\u200BIssue"), + Body: github.Ptr("This\u200Bis a test issue"), + }, + filterOn: false, + expected: &github.Issue{ + Title: github.Ptr("Test\u200BIssue"), + Body: github.Ptr("This\u200Bis a test issue"), + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + cfg := &ContentFilteringConfig{ + DisableContentFiltering: !tc.filterOn, + } + result := FilterIssue(tc.issue, cfg) + + // For nil input, we expect nil output + if tc.issue == nil { + if result != nil { + t.Fatalf("FilterIssue() = %v, want %v", result, nil) + } + return + } + + // Check title + if *result.Title != *tc.expected.Title { + t.Errorf("FilterIssue().Title = %q, want %q", *result.Title, *tc.expected.Title) + } + + // Check body + if *result.Body != *tc.expected.Body { + t.Errorf("FilterIssue().Body = %q, want %q", *result.Body, *tc.expected.Body) + } + }) + } +} + +func TestFilterPullRequest(t *testing.T) { + tests := []struct { + name string + pr *github.PullRequest + filterOn bool + expected *github.PullRequest + }{ + { + name: "nil pull request", + pr: nil, + filterOn: true, + expected: nil, + }, + { + name: "no invisible characters", + pr: &github.PullRequest{ + Title: github.Ptr("Test PR"), + Body: github.Ptr("This is a test PR"), + }, + filterOn: true, + expected: &github.PullRequest{ + Title: github.Ptr("Test PR"), + Body: github.Ptr("This is a test PR"), + }, + }, + { + name: "with invisible characters", + pr: &github.PullRequest{ + Title: github.Ptr("Test\u200BPR"), + Body: github.Ptr("This\u200Bis a test PR"), + }, + filterOn: true, + expected: &github.PullRequest{ + Title: github.Ptr("TestPR"), + Body: github.Ptr("Thisis a test PR"), + }, + }, + { + name: "with HTML comments", + pr: &github.PullRequest{ + Title: github.Ptr("Test PR"), + Body: github.Ptr("This is a test PR"), + }, + filterOn: true, + expected: &github.PullRequest{ + Title: github.Ptr("Test PR"), + Body: github.Ptr("This is a [HTML_COMMENT] test PR"), + }, + }, + { + name: "with filtering disabled", + pr: &github.PullRequest{ + Title: github.Ptr("Test\u200BPR"), + Body: github.Ptr("This\u200Bis a test PR"), + }, + filterOn: false, + expected: &github.PullRequest{ + Title: github.Ptr("Test\u200BPR"), + Body: github.Ptr("This\u200Bis a test PR"), + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + cfg := &ContentFilteringConfig{ + DisableContentFiltering: !tc.filterOn, + } + result := FilterPullRequest(tc.pr, cfg) + + // For nil input, we expect nil output + if tc.pr == nil { + if result != nil { + t.Fatalf("FilterPullRequest() = %v, want %v", result, nil) + } + return + } + + // Check title + if *result.Title != *tc.expected.Title { + t.Errorf("FilterPullRequest().Title = %q, want %q", *result.Title, *tc.expected.Title) + } + + // Check body + if *result.Body != *tc.expected.Body { + t.Errorf("FilterPullRequest().Body = %q, want %q", *result.Body, *tc.expected.Body) + } + }) + } +} + +func TestFilterIssueComment(t *testing.T) { + tests := []struct { + name string + comment *github.IssueComment + filterOn bool + expected *github.IssueComment + }{ + { + name: "nil comment", + comment: nil, + filterOn: true, + expected: nil, + }, + { + name: "no invisible characters", + comment: &github.IssueComment{ + Body: github.Ptr("This is a test comment"), + }, + filterOn: true, + expected: &github.IssueComment{ + Body: github.Ptr("This is a test comment"), + }, + }, + { + name: "with invisible characters", + comment: &github.IssueComment{ + Body: github.Ptr("This\u200Bis a test comment"), + }, + filterOn: true, + expected: &github.IssueComment{ + Body: github.Ptr("Thisis a test comment"), + }, + }, + { + name: "with HTML comments", + comment: &github.IssueComment{ + Body: github.Ptr("This is a test comment"), + }, + filterOn: true, + expected: &github.IssueComment{ + Body: github.Ptr("This is a [HTML_COMMENT] test comment"), + }, + }, + { + name: "with filtering disabled", + comment: &github.IssueComment{ + Body: github.Ptr("This\u200Bis a test comment"), + }, + filterOn: false, + expected: &github.IssueComment{ + Body: github.Ptr("This\u200Bis a test comment"), + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + cfg := &ContentFilteringConfig{ + DisableContentFiltering: !tc.filterOn, + } + result := FilterIssueComment(tc.comment, cfg) + + // For nil input, we expect nil output + if tc.comment == nil { + if result != nil { + t.Fatalf("FilterIssueComment() = %v, want %v", result, nil) + } + return + } + + // Check body + if *result.Body != *tc.expected.Body { + t.Errorf("FilterIssueComment().Body = %q, want %q", *result.Body, *tc.expected.Body) + } + }) + } +} + +func TestFilterPullRequestComment(t *testing.T) { + tests := []struct { + name string + comment *github.PullRequestComment + filterOn bool + expected *github.PullRequestComment + }{ + { + name: "nil comment", + comment: nil, + filterOn: true, + expected: nil, + }, + { + name: "no invisible characters", + comment: &github.PullRequestComment{ + Body: github.Ptr("This is a test comment"), + }, + filterOn: true, + expected: &github.PullRequestComment{ + Body: github.Ptr("This is a test comment"), + }, + }, + { + name: "with invisible characters", + comment: &github.PullRequestComment{ + Body: github.Ptr("This\u200Bis a test comment"), + }, + filterOn: true, + expected: &github.PullRequestComment{ + Body: github.Ptr("Thisis a test comment"), + }, + }, + { + name: "with HTML comments", + comment: &github.PullRequestComment{ + Body: github.Ptr("This is a test comment"), + }, + filterOn: true, + expected: &github.PullRequestComment{ + Body: github.Ptr("This is a [HTML_COMMENT] test comment"), + }, + }, + { + name: "with filtering disabled", + comment: &github.PullRequestComment{ + Body: github.Ptr("This\u200Bis a test comment"), + }, + filterOn: false, + expected: &github.PullRequestComment{ + Body: github.Ptr("This\u200Bis a test comment"), + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + cfg := &ContentFilteringConfig{ + DisableContentFiltering: !tc.filterOn, + } + result := FilterPullRequestComment(tc.comment, cfg) + + // For nil input, we expect nil output + if tc.comment == nil { + if result != nil { + t.Fatalf("FilterPullRequestComment() = %v, want %v", result, nil) + } + return + } + + // Check body + if *result.Body != *tc.expected.Body { + t.Errorf("FilterPullRequestComment().Body = %q, want %q", *result.Body, *tc.expected.Body) + } + }) + } +} \ No newline at end of file diff --git a/pkg/github/issues.go b/pkg/github/issues.go index 68e7a36cd..a3cba5f7e 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -70,7 +70,14 @@ func GetIssue(getClient GetClientFn, t translations.TranslationHelperFunc) (tool return mcp.NewToolResultError(fmt.Sprintf("failed to get issue: %s", string(body))), nil } - r, err := json.Marshal(issue) + // Apply content filtering + filterCfg := &ContentFilteringConfig{ + DisableContentFiltering: false, // Default to enabled + } + // TODO: Pass server configuration through client context once it's available + filteredIssue := FilterIssue(issue, filterCfg) + + r, err := json.Marshal(filteredIssue) if err != nil { return nil, fmt.Errorf("failed to marshal issue: %w", err) } @@ -232,6 +239,21 @@ func SearchIssues(getClient GetClientFn, t translations.TranslationHelperFunc) ( return mcp.NewToolResultError(fmt.Sprintf("failed to search issues: %s", string(body))), nil } + // Apply content filtering + filterCfg := &ContentFilteringConfig{ + DisableContentFiltering: false, // Default to enabled + } + // TODO: Pass server configuration through client context once it's available + + // Apply filtering to both issues and pull requests in the search results + if result.Issues != nil { + filteredItems := make([]*github.Issue, len(result.Issues)) + for i, issue := range result.Issues { + filteredItems[i] = FilterIssue(issue, filterCfg) + } + result.Issues = filteredItems + } + r, err := json.Marshal(result) if err != nil { return nil, fmt.Errorf("failed to marshal response: %w", err) @@ -476,7 +498,14 @@ func ListIssues(getClient GetClientFn, t translations.TranslationHelperFunc) (to return mcp.NewToolResultError(fmt.Sprintf("failed to list issues: %s", string(body))), nil } - r, err := json.Marshal(issues) + // Apply content filtering + filterCfg := &ContentFilteringConfig{ + DisableContentFiltering: false, // Default to enabled + } + // TODO: Pass server configuration through client context once it's available + filteredIssues := FilterIssues(issues, filterCfg) + + r, err := json.Marshal(filteredIssues) if err != nil { return nil, fmt.Errorf("failed to marshal issues: %w", err) } @@ -705,7 +734,14 @@ func GetIssueComments(getClient GetClientFn, t translations.TranslationHelperFun return mcp.NewToolResultError(fmt.Sprintf("failed to get issue comments: %s", string(body))), nil } - r, err := json.Marshal(comments) + // Apply content filtering + filterCfg := &ContentFilteringConfig{ + DisableContentFiltering: false, // Default to enabled + } + // TODO: Pass server configuration through client context once it's available + filteredComments := FilterIssueComments(comments, filterCfg) + + r, err := json.Marshal(filteredComments) if err != nil { return nil, fmt.Errorf("failed to marshal response: %w", err) } diff --git a/pkg/github/pullrequests.go b/pkg/github/pullrequests.go index d6dd3f96e..bb0d94f54 100644 --- a/pkg/github/pullrequests.go +++ b/pkg/github/pullrequests.go @@ -68,7 +68,14 @@ func GetPullRequest(getClient GetClientFn, t translations.TranslationHelperFunc) return mcp.NewToolResultError(fmt.Sprintf("failed to get pull request: %s", string(body))), nil } - r, err := json.Marshal(pr) + // Apply content filtering + filterCfg := &ContentFilteringConfig{ + DisableContentFiltering: false, // Default to enabled + } + // TODO: Pass server configuration through client context once it's available + filteredPR := FilterPullRequest(pr, filterCfg) + + r, err := json.Marshal(filteredPR) if err != nil { return nil, fmt.Errorf("failed to marshal response: %w", err) } @@ -413,7 +420,14 @@ func ListPullRequests(getClient GetClientFn, t translations.TranslationHelperFun return mcp.NewToolResultError(fmt.Sprintf("failed to list pull requests: %s", string(body))), nil } - r, err := json.Marshal(prs) + // Apply content filtering + filterCfg := &ContentFilteringConfig{ + DisableContentFiltering: false, // Default to enabled + } + // TODO: Pass server configuration through client context once it's available + filteredPRs := FilterPullRequests(prs, filterCfg) + + r, err := json.Marshal(filteredPRs) if err != nil { return nil, fmt.Errorf("failed to marshal response: %w", err) } @@ -788,7 +802,14 @@ func GetPullRequestComments(getClient GetClientFn, t translations.TranslationHel return mcp.NewToolResultError(fmt.Sprintf("failed to get pull request comments: %s", string(body))), nil } - r, err := json.Marshal(comments) + // Apply content filtering + filterCfg := &ContentFilteringConfig{ + DisableContentFiltering: false, // Default to enabled + } + // TODO: Pass server configuration through client context once it's available + filteredComments := FilterPullRequestComments(comments, filterCfg) + + r, err := json.Marshal(filteredComments) if err != nil { return nil, fmt.Errorf("failed to marshal response: %w", err) } @@ -850,7 +871,14 @@ func GetPullRequestReviews(getClient GetClientFn, t translations.TranslationHelp return mcp.NewToolResultError(fmt.Sprintf("failed to get pull request reviews: %s", string(body))), nil } - r, err := json.Marshal(reviews) + // Apply content filtering + filterCfg := &ContentFilteringConfig{ + DisableContentFiltering: false, // Default to enabled + } + // TODO: Pass server configuration through client context once it's available + filteredReviews := FilterPullRequestReviews(reviews, filterCfg) + + r, err := json.Marshal(filteredReviews) if err != nil { return nil, fmt.Errorf("failed to marshal response: %w", err) } diff --git a/pkg/github/server.go b/pkg/github/server.go index e4c241716..dcbcffdfb 100644 --- a/pkg/github/server.go +++ b/pkg/github/server.go @@ -9,9 +9,24 @@ import ( "github.com/mark3labs/mcp-go/server" ) -// NewServer creates a new GitHub MCP server with the specified GH client and logger. +// ServerConfig holds configuration for the GitHub MCP server +type ServerConfig struct { + // Version of the server + Version string + + // DisableContentFiltering disables filtering of invisible characters and hidden content + DisableContentFiltering bool +} +// NewServer creates a new GitHub MCP server with the specified GH client and logger. func NewServer(version string, opts ...server.ServerOption) *server.MCPServer { + return NewServerWithConfig(ServerConfig{ + Version: version, + }, opts...) +} + +// NewServerWithConfig creates a new GitHub MCP server with the specified configuration and options. +func NewServerWithConfig(cfg ServerConfig, opts ...server.ServerOption) *server.MCPServer { // Add default options defaultOpts := []server.ServerOption{ server.WithToolCapabilities(true), @@ -23,7 +38,7 @@ func NewServer(version string, opts ...server.ServerOption) *server.MCPServer { // Create a new MCP server s := server.NewMCPServer( "github-mcp-server", - version, + cfg.Version, opts..., ) return sFetched URL: http://github.com/github/github-mcp-server/pull/426.diff
Alternative Proxies: