From 5badfe6004253634c8921808bd95bb75c2c06519 Mon Sep 17 00:00:00 2001 From: tonytrg Date: Tue, 14 Oct 2025 16:54:20 +0200 Subject: [PATCH 1/6] adding better response --- pkg/github/repositories.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index dfd718f7e..3f56025db 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -542,6 +542,8 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t // If the path is (most likely) not to be a directory, we will // first try to get the raw content from the GitHub raw content API. + + var rawAPIResponseCode int if path != "" && !strings.HasSuffix(path, "/") { // First, get file info from Contents API to retrieve SHA var fileSHA string @@ -631,8 +633,8 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t return mcp.NewToolResultResource(fmt.Sprintf("successfully downloaded binary file (SHA: %s)", fileSHA), result), nil } return mcp.NewToolResultResource("successfully downloaded binary file", result), nil - } + rawAPIResponseCode = resp.StatusCode } if rawOpts.SHA != "" { @@ -677,7 +679,7 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t if err != nil { return mcp.NewToolResultError(fmt.Sprintf("failed to marshal resolved refs: %s", err)), nil } - return mcp.NewToolResultText(fmt.Sprintf("Path did not point to a file or directory, but resolved git ref to %s with possible path matches: %s", resolvedRefs, matchingFilesJSON)), nil + return mcp.NewToolResultText(fmt.Sprintf("Failed to get raw repository content with response %d, but resolved git ref to %s with possible path matches: %s", rawAPIResponseCode, resolvedRefs, matchingFilesJSON)), nil } return mcp.NewToolResultError("Failed to get file contents. The path does not point to a file or directory, or the file does not exist in the repository."), nil From e6fa90df6e7ef40c268581cfd131586ece260acd Mon Sep 17 00:00:00 2001 From: tonytrg Date: Tue, 14 Oct 2025 16:56:40 +0200 Subject: [PATCH 2/6] adding check for subdomain isolation and return raw resp for better better llm response --- internal/ghmcp/server.go | 39 +++++++++++++++++++++++++++++++++++++- pkg/github/repositories.go | 1 + 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index 2fb2fb19b..23f2e331e 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -12,6 +12,7 @@ import ( "os/signal" "strings" "syscall" + "time" "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/github" @@ -367,7 +368,18 @@ func newGHESHost(hostname string) (apiHost, error) { if err != nil { return apiHost{}, fmt.Errorf("failed to parse GHES Upload URL: %w", err) } - rawURL, err := url.Parse(fmt.Sprintf("%s://%s/raw/", u.Scheme, u.Hostname())) + + // Check if subdomain isolation is enabled + hasSubdomainIsolation := checkSubdomainIsolation(u.Scheme, u.Hostname()) + + var rawURL *url.URL + if hasSubdomainIsolation { + // With subdomain isolation: https://raw.hostname/ + rawURL, err = url.Parse(fmt.Sprintf("%s://raw.%s/", u.Scheme, u.Hostname())) + } else { + // Without subdomain isolation: https://hostname/raw/ + rawURL, err = url.Parse(fmt.Sprintf("%s://%s/raw/", u.Scheme, u.Hostname())) + } if err != nil { return apiHost{}, fmt.Errorf("failed to parse GHES Raw URL: %w", err) } @@ -380,6 +392,31 @@ func newGHESHost(hostname string) (apiHost, error) { }, nil } +// checkSubdomainIsolation detects if GitHub Enterprise Server has subdomain isolation enabled +// by attempting to ping the raw._ping endpoint on the subdomain. +// Returns true if subdomain isolation is detected, false otherwise. +func checkSubdomainIsolation(scheme, hostname string) bool { + // Try the subdomain isolation URL first: https://raw.hostname/_ping + subdomainURL := fmt.Sprintf("%s://raw.%s/_ping", scheme, hostname) + + client := &http.Client{ + Timeout: 5 * time.Second, + // Don't follow redirects - we just want to check if the endpoint exists + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } + + resp, err := client.Get(subdomainURL) + if err != nil { + // If we can't reach the subdomain, assume no subdomain isolation + return false + } + defer resp.Body.Close() + + return resp.StatusCode == http.StatusOK +} + // Note that this does not handle ports yet, so development environments are out. func parseAPIHost(s string) (apiHost, error) { if s == "" { diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index 3f56025db..182a49b0f 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -8,6 +8,7 @@ import ( "io" "net/http" "net/url" + "os" "strings" ghErrors "github.com/github/github-mcp-server/pkg/errors" From 1a4998ca51ad671d3e621f68584e5570b6f34c2e Mon Sep 17 00:00:00 2001 From: tonytrg Date: Tue, 14 Oct 2025 17:01:45 +0200 Subject: [PATCH 3/6] adding subdomain for uploads too --- internal/ghmcp/server.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index 23f2e331e..112f034c8 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -364,14 +364,22 @@ func newGHESHost(hostname string) (apiHost, error) { return apiHost{}, fmt.Errorf("failed to parse GHES GraphQL URL: %w", err) } - uploadURL, err := url.Parse(fmt.Sprintf("%s://%s/api/uploads/", u.Scheme, u.Hostname())) + // Check if subdomain isolation is enabled + // See https://docs.github.com/en/enterprise-server@3.17/admin/configuring-settings/hardening-security-for-your-enterprise/enabling-subdomain-isolation#about-subdomain-isolation + hasSubdomainIsolation := checkSubdomainIsolation(u.Scheme, u.Hostname()) + + var uploadURL *url.URL + if hasSubdomainIsolation { + // With subdomain isolation: https://uploads.hostname/ + uploadURL, err = url.Parse(fmt.Sprintf("%s://uploads.%s/", u.Scheme, u.Hostname())) + } else { + // Without subdomain isolation: https://hostname/api/uploads/ + uploadURL, err = url.Parse(fmt.Sprintf("%s://%s/api/uploads/", u.Scheme, u.Hostname())) + } if err != nil { return apiHost{}, fmt.Errorf("failed to parse GHES Upload URL: %w", err) } - // Check if subdomain isolation is enabled - hasSubdomainIsolation := checkSubdomainIsolation(u.Scheme, u.Hostname()) - var rawURL *url.URL if hasSubdomainIsolation { // With subdomain isolation: https://raw.hostname/ From a97eaa658e0f2db4d663808c23dcbc644c2fe744 Mon Sep 17 00:00:00 2001 From: tonytrg Date: Tue, 14 Oct 2025 17:03:48 +0200 Subject: [PATCH 4/6] remove unnecessary comments --- internal/ghmcp/server.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index 112f034c8..c097dc88d 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -401,10 +401,8 @@ func newGHESHost(hostname string) (apiHost, error) { } // checkSubdomainIsolation detects if GitHub Enterprise Server has subdomain isolation enabled -// by attempting to ping the raw._ping endpoint on the subdomain. -// Returns true if subdomain isolation is detected, false otherwise. +// by attempting to ping the raw./_ping endpoint on the subdomain. The raw subdomain must always exist for subdomain isolation. func checkSubdomainIsolation(scheme, hostname string) bool { - // Try the subdomain isolation URL first: https://raw.hostname/_ping subdomainURL := fmt.Sprintf("%s://raw.%s/_ping", scheme, hostname) client := &http.Client{ @@ -417,7 +415,6 @@ func checkSubdomainIsolation(scheme, hostname string) bool { resp, err := client.Get(subdomainURL) if err != nil { - // If we can't reach the subdomain, assume no subdomain isolation return false } defer resp.Body.Close() From ab846102af2795eb79316002d7999c2d868d3f85 Mon Sep 17 00:00:00 2001 From: tonytrg Date: Tue, 14 Oct 2025 17:11:09 +0200 Subject: [PATCH 5/6] better error message --- pkg/github/repositories.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index 182a49b0f..7ffc5fc0c 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -8,7 +8,6 @@ import ( "io" "net/http" "net/url" - "os" "strings" ghErrors "github.com/github/github-mcp-server/pkg/errors" @@ -680,7 +679,7 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t if err != nil { return mcp.NewToolResultError(fmt.Sprintf("failed to marshal resolved refs: %s", err)), nil } - return mcp.NewToolResultText(fmt.Sprintf("Failed to get raw repository content with response %d, but resolved git ref to %s with possible path matches: %s", rawAPIResponseCode, resolvedRefs, matchingFilesJSON)), nil + return mcp.NewToolResultError(fmt.Sprintf("Resolved potential matches in the repository tree (resolved refs: %s, matching files: %s), but the raw content API returned an unexpected status code %d.", string(resolvedRefs), string(matchingFilesJSON), rawAPIResponseCode)), nil } return mcp.NewToolResultError("Failed to get file contents. The path does not point to a file or directory, or the file does not exist in the repository."), nil From cb201fd4ebcba2c44d374306d2f1bfebe8e7244e Mon Sep 17 00:00:00 2001 From: tonytrg Date: Tue, 14 Oct 2025 17:21:31 +0200 Subject: [PATCH 6/6] fix linter --- internal/ghmcp/server.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index c097dc88d..cb44dffa0 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -408,6 +408,7 @@ func checkSubdomainIsolation(scheme, hostname string) bool { client := &http.Client{ Timeout: 5 * time.Second, // Don't follow redirects - we just want to check if the endpoint exists + //nolint:revive // parameters are required by http.Client.CheckRedirect signature CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse },