Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit36f33e9

Browse files
use better raw file handling and return resources
1 parentc423a52 commit36f33e9

File tree

18 files changed

+810
-286
lines changed

18 files changed

+810
-286
lines changed

‎internal/ghmcp/server.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
"github.com/github/github-mcp-server/pkg/github"
1616
mcplog"github.com/github/github-mcp-server/pkg/log"
17+
"github.com/github/github-mcp-server/pkg/raw"
1718
"github.com/github/github-mcp-server/pkg/translations"
1819
gogithub"github.com/google/go-github/v72/github"
1920
"github.com/mark3labs/mcp-go/mcp"
@@ -112,8 +113,16 @@ func NewMCPServer(cfg MCPServerConfig) (*server.MCPServer, error) {
112113
returngqlClient,nil// closing over client
113114
}
114115

116+
getRawClient:=func(ctx context.Context) (*raw.Client,error) {
117+
client,err:=getClient(ctx)
118+
iferr!=nil {
119+
returnnil,fmt.Errorf("failed to get GitHub client: %w",err)
120+
}
121+
returnraw.NewClient(client,apiHost.rawURL),nil// closing over client
122+
}
123+
115124
// Create default toolsets
116-
tsg:=github.DefaultToolsetGroup(cfg.ReadOnly,getClient,getGQLClient,cfg.Translator)
125+
tsg:=github.DefaultToolsetGroup(cfg.ReadOnly,getClient,getGQLClient,getRawClient,cfg.Translator)
117126
err=tsg.EnableToolsets(enabledToolsets)
118127

119128
iferr!=nil {
@@ -237,6 +246,7 @@ type apiHost struct {
237246
baseRESTURL*url.URL
238247
graphqlURL*url.URL
239248
uploadURL*url.URL
249+
rawURL*url.URL
240250
}
241251

242252
funcnewDotcomHost() (apiHost,error) {
@@ -255,10 +265,16 @@ func newDotcomHost() (apiHost, error) {
255265
returnapiHost{},fmt.Errorf("failed to parse dotcom Upload URL: %w",err)
256266
}
257267

268+
rawURL,err:=url.Parse("https://raw.githubusercontent.com/")
269+
iferr!=nil {
270+
returnapiHost{},fmt.Errorf("failed to parse dotcom Raw URL: %w",err)
271+
}
272+
258273
returnapiHost{
259274
baseRESTURL:baseRestURL,
260275
graphqlURL:gqlURL,
261276
uploadURL:uploadURL,
277+
rawURL:rawURL,
262278
},nil
263279
}
264280

@@ -288,10 +304,16 @@ func newGHECHost(hostname string) (apiHost, error) {
288304
returnapiHost{},fmt.Errorf("failed to parse GHEC Upload URL: %w",err)
289305
}
290306

307+
rawURL,err:=url.Parse(fmt.Sprintf("https://raw.%s/",u.Hostname()))
308+
iferr!=nil {
309+
returnapiHost{},fmt.Errorf("failed to parse GHEC Raw URL: %w",err)
310+
}
311+
291312
returnapiHost{
292313
baseRESTURL:restURL,
293314
graphqlURL:gqlURL,
294315
uploadURL:uploadURL,
316+
rawURL:rawURL,
295317
},nil
296318
}
297319

@@ -315,11 +337,16 @@ func newGHESHost(hostname string) (apiHost, error) {
315337
iferr!=nil {
316338
returnapiHost{},fmt.Errorf("failed to parse GHES Upload URL: %w",err)
317339
}
340+
rawURL,err:=url.Parse(fmt.Sprintf("%s://%s/raw/",u.Scheme,u.Hostname()))
341+
iferr!=nil {
342+
returnapiHost{},fmt.Errorf("failed to parse GHES Raw URL: %w",err)
343+
}
318344

319345
returnapiHost{
320346
baseRESTURL:restURL,
321347
graphqlURL:gqlURL,
322348
uploadURL:uploadURL,
349+
rawURL:rawURL,
323350
},nil
324351
}
325352

‎pkg/github/helper_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,36 @@ func getTextResult(t *testing.T, result *mcp.CallToolResult) mcp.TextContent {
132132
returntextContent
133133
}
134134

135+
funcgetErrorResult(t*testing.T,result*mcp.CallToolResult) mcp.TextContent {
136+
res:=getTextResult(t,result)
137+
require.True(t,result.IsError,"expected tool call result to be an error")
138+
returnres
139+
}
140+
141+
// getTextResourceResultis a helper function that returns a text result from a tool call.
142+
funcgetTextResourceResult(t*testing.T,result*mcp.CallToolResult) mcp.TextResourceContents {
143+
t.Helper()
144+
assert.NotNil(t,result)
145+
require.Len(t,result.Content,2)
146+
content:=result.Content[1]
147+
require.IsType(t, mcp.EmbeddedResource{},content)
148+
resource:=content.(mcp.EmbeddedResource)
149+
require.IsType(t, mcp.TextResourceContents{},resource.Resource)
150+
returnresource.Resource.(mcp.TextResourceContents)
151+
}
152+
153+
// getBlobResourceResult is a helper function that returns a blob result from a tool call.
154+
funcgetBlobResourceResult(t*testing.T,result*mcp.CallToolResult) mcp.BlobResourceContents {
155+
t.Helper()
156+
assert.NotNil(t,result)
157+
require.Len(t,result.Content,2)
158+
content:=result.Content[1]
159+
require.IsType(t, mcp.EmbeddedResource{},content)
160+
resource:=content.(mcp.EmbeddedResource)
161+
require.IsType(t, mcp.BlobResourceContents{},resource.Resource)
162+
returnresource.Resource.(mcp.BlobResourceContents)
163+
}
164+
135165
funcTestOptionalParamOK(t*testing.T) {
136166
tests:= []struct {
137167
namestring

‎pkg/github/repositories.go

Lines changed: 83 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ package github
22

33
import (
44
"context"
5+
"encoding/base64"
56
"encoding/json"
67
"fmt"
78
"io"
89
"net/http"
10+
"net/url"
11+
"strings"
912

13+
"github.com/github/github-mcp-server/pkg/raw"
1014
"github.com/github/github-mcp-server/pkg/translations"
1115
"github.com/google/go-github/v72/github"
1216
"github.com/mark3labs/mcp-go/mcp"
@@ -409,7 +413,7 @@ func CreateRepository(getClient GetClientFn, t translations.TranslationHelperFun
409413
}
410414

411415
// GetFileContents creates a tool to get the contents of a file or directory from a GitHub repository.
412-
funcGetFileContents(getClientGetClientFn,t translations.TranslationHelperFunc) (tool mcp.Tool,handler server.ToolHandlerFunc) {
416+
funcGetFileContents(getClientGetClientFn,getRawClient raw.GetRawClientFn,t translations.TranslationHelperFunc) (tool mcp.Tool,handler server.ToolHandlerFunc) {
413417
returnmcp.NewTool("get_file_contents",
414418
mcp.WithDescription(t("TOOL_GET_FILE_CONTENTS_DESCRIPTION","Get the contents of a file or directory from a GitHub repository")),
415419
mcp.WithToolAnnotation(mcp.ToolAnnotation{
@@ -426,7 +430,7 @@ func GetFileContents(getClient GetClientFn, t translations.TranslationHelperFunc
426430
),
427431
mcp.WithString("path",
428432
mcp.Required(),
429-
mcp.Description("Path to file/directory"),
433+
mcp.Description("Path to file/directory (directories must end with a slash '/')"),
430434
),
431435
mcp.WithString("branch",
432436
mcp.Description("Branch to get contents from"),
@@ -450,38 +454,92 @@ func GetFileContents(getClient GetClientFn, t translations.TranslationHelperFunc
450454
returnmcp.NewToolResultError(err.Error()),nil
451455
}
452456

453-
client,err:=getClient(ctx)
454-
iferr!=nil {
455-
returnnil,fmt.Errorf("failed to get GitHub client: %w",err)
457+
// 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.
458+
ifpath!=""&&!strings.HasSuffix(path,"/") {
459+
rawOpts:=&raw.RawContentOpts{}
460+
ifbranch!="" {
461+
rawOpts.Ref="refs/heads/"+branch
462+
}
463+
rawClient,err:=getRawClient(ctx)
464+
iferr!=nil {
465+
returnmcp.NewToolResultError("failed to get GitHub raw content client"),nil
466+
}
467+
resp,err:=rawClient.GetRawContent(ctx,owner,repo,path,rawOpts)
468+
iferr!=nil {
469+
returnmcp.NewToolResultError("failed to get raw repository content"),nil
470+
}
471+
deferfunc() {
472+
_=resp.Body.Close()
473+
}()
474+
475+
ifresp.StatusCode!=http.StatusOK {
476+
// If the raw content is not found, we will fall back to the GitHub API (in case it is a directory)
477+
}else {
478+
// If the raw content is found, return it directly
479+
body,err:=io.ReadAll(resp.Body)
480+
iferr!=nil {
481+
returnmcp.NewToolResultError("failed to read response body"),nil
482+
}
483+
contentType:=resp.Header.Get("Content-Type")
484+
485+
varresourceURIstring
486+
ifbranch=="" {
487+
// do a safe url join
488+
resourceURI,err=url.JoinPath("repo://",owner,repo,"contents",path)
489+
iferr!=nil {
490+
returnnil,fmt.Errorf("failed to create resource URI: %w",err)
491+
}
492+
}else {
493+
resourceURI,err=url.JoinPath("repo://",owner,repo,"refs","heads",branch,"contents",path)
494+
iferr!=nil {
495+
returnnil,fmt.Errorf("failed to create resource URI: %w",err)
496+
}
497+
}
498+
ifstrings.HasPrefix(contentType,"application")||strings.HasPrefix(contentType,"text") {
499+
returnmcp.NewToolResultResource("successfully downloaded text file", mcp.TextResourceContents{
500+
URI:resourceURI,
501+
Text:string(body),
502+
MIMEType:contentType,
503+
}),nil
504+
}
505+
506+
returnmcp.NewToolResultResource("successfully downloaded binary file", mcp.BlobResourceContents{
507+
URI:resourceURI,
508+
Blob:base64.StdEncoding.EncodeToString(body),
509+
MIMEType:contentType,
510+
}),nil
511+
512+
}
456513
}
457-
opts:=&github.RepositoryContentGetOptions{Ref:branch}
458-
fileContent,dirContent,resp,err:=client.Repositories.GetContents(ctx,owner,repo,path,opts)
514+
515+
client,err:=getClient(ctx)
459516
iferr!=nil {
460-
returnnil,fmt.Errorf("failed to getfile contents: %w",err)
517+
returnmcp.NewToolResultError("failed to getGitHub client"),nil
461518
}
462-
deferfunc() {_=resp.Body.Close() }()
463519

464-
ifresp.StatusCode!=200 {
465-
body,err:=io.ReadAll(resp.Body)
520+
ifstrings.HasSuffix(path,"/") {
521+
opts:=&github.RepositoryContentGetOptions{Ref:branch}
522+
_,dirContent,resp,err:=client.Repositories.GetContents(ctx,owner,repo,path,opts)
466523
iferr!=nil {
467-
returnnil,fmt.Errorf("failed toread response body: %w",err)
524+
returnmcp.NewToolResultError("failed toget file contents"),nil
468525
}
469-
returnmcp.NewToolResultError(fmt.Sprintf("failed to get file contents: %s",string(body))),nil
470-
}
526+
deferfunc() {_=resp.Body.Close() }()
471527

472-
varresultinterface{}
473-
iffileContent!=nil {
474-
result=fileContent
475-
}else {
476-
result=dirContent
477-
}
528+
ifresp.StatusCode!=200 {
529+
body,err:=io.ReadAll(resp.Body)
530+
iferr!=nil {
531+
returnmcp.NewToolResultError("failed to read response body"),nil
532+
}
533+
returnmcp.NewToolResultError(fmt.Sprintf("failed to get file contents: %s",string(body))),nil
534+
}
478535

479-
r,err:=json.Marshal(result)
480-
iferr!=nil {
481-
returnnil,fmt.Errorf("failed to marshal response: %w",err)
536+
r,err:=json.Marshal(dirContent)
537+
iferr!=nil {
538+
returnmcp.NewToolResultError("failed to marshal response"),nil
539+
}
540+
returnmcp.NewToolResultText(string(r)),nil
482541
}
483-
484-
returnmcp.NewToolResultText(string(r)),nil
542+
returnmcp.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
485543
}
486544
}
487545

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp