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

Commit1dea445

Browse files
feat: partition tools by product/feature
1 parent62eed34 commit1dea445

16 files changed

+877
-245
lines changed

‎cmd/github-mcp-server/main.go

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,16 @@ var (
4444
iferr!=nil {
4545
stdlog.Fatal("Failed to initialize logger:",err)
4646
}
47+
48+
enabledToolsets:=viper.GetStringSlice("toolsets")
49+
4750
logCommands:=viper.GetBool("enable-command-logging")
4851
cfg:=runConfig{
4952
readOnly:readOnly,
5053
logger:logger,
5154
logCommands:logCommands,
5255
exportTranslations:exportTranslations,
56+
enabledToolsets:enabledToolsets,
5357
}
5458
iferr:=runStdioServer(cfg);err!=nil {
5559
stdlog.Fatal("failed to run stdio server:",err)
@@ -62,26 +66,30 @@ func init() {
6266
cobra.OnInitialize(initConfig)
6367

6468
// Add global flags that will be shared by all commands
69+
rootCmd.PersistentFlags().StringSlice("toolsets",github.DefaultTools,"An optional comma separated list of groups of tools to allow, defaults to enabling all")
70+
rootCmd.PersistentFlags().Bool("dynamic-toolsets",false,"Enable dynamic toolsets")
6571
rootCmd.PersistentFlags().Bool("read-only",false,"Restrict the server to read-only operations")
6672
rootCmd.PersistentFlags().String("log-file","","Path to log file")
6773
rootCmd.PersistentFlags().Bool("enable-command-logging",false,"When enabled, the server will log all command requests and responses to the log file")
6874
rootCmd.PersistentFlags().Bool("export-translations",false,"Save translations to a JSON file")
6975
rootCmd.PersistentFlags().String("gh-host","","Specify the GitHub hostname (for GitHub Enterprise etc.)")
7076

7177
// Bind flag to viper
78+
_=viper.BindPFlag("toolsets",rootCmd.PersistentFlags().Lookup("toolsets"))
79+
_=viper.BindPFlag("dynamic_toolsets",rootCmd.PersistentFlags().Lookup("dynamic-toolsets"))
7280
_=viper.BindPFlag("read-only",rootCmd.PersistentFlags().Lookup("read-only"))
7381
_=viper.BindPFlag("log-file",rootCmd.PersistentFlags().Lookup("log-file"))
7482
_=viper.BindPFlag("enable-command-logging",rootCmd.PersistentFlags().Lookup("enable-command-logging"))
7583
_=viper.BindPFlag("export-translations",rootCmd.PersistentFlags().Lookup("export-translations"))
76-
_=viper.BindPFlag("gh-host",rootCmd.PersistentFlags().Lookup("gh-host"))
84+
_=viper.BindPFlag("host",rootCmd.PersistentFlags().Lookup("gh-host"))
7785

7886
// Add subcommands
7987
rootCmd.AddCommand(stdioCmd)
8088
}
8189

8290
funcinitConfig() {
8391
// Initialize Viper configuration
84-
viper.SetEnvPrefix("APP")
92+
viper.SetEnvPrefix("github")
8593
viper.AutomaticEnv()
8694
}
8795

@@ -107,6 +115,7 @@ type runConfig struct {
107115
logger*log.Logger
108116
logCommandsbool
109117
exportTranslationsbool
118+
enabledToolsets []string
110119
}
111120

112121
funcrunStdioServer(cfgrunConfig)error {
@@ -115,18 +124,14 @@ func runStdioServer(cfg runConfig) error {
115124
deferstop()
116125

117126
// Create GH client
118-
token:=os.Getenv("GITHUB_PERSONAL_ACCESS_TOKEN")
127+
token:=viper.GetString("personal_access_token")
119128
iftoken=="" {
120129
cfg.logger.Fatal("GITHUB_PERSONAL_ACCESS_TOKEN not set")
121130
}
122131
ghClient:=gogithub.NewClient(nil).WithAuthToken(token)
123132
ghClient.UserAgent=fmt.Sprintf("github-mcp-server/%s",version)
124133

125-
// Check GH_HOST env var first, then fall back to viper config
126-
host:=os.Getenv("GH_HOST")
127-
ifhost=="" {
128-
host=viper.GetString("gh-host")
129-
}
134+
host:=viper.GetString("host")
130135

131136
ifhost!="" {
132137
varerrerror
@@ -149,8 +154,40 @@ func runStdioServer(cfg runConfig) error {
149154
hooks:=&server.Hooks{
150155
OnBeforeInitialize: []server.OnBeforeInitializeFunc{beforeInit},
151156
}
152-
// Create
153-
ghServer:=github.NewServer(getClient,version,cfg.readOnly,t,server.WithHooks(hooks))
157+
// Create server
158+
ghServer:=github.NewServer(version,server.WithHooks(hooks))
159+
160+
enabled:=cfg.enabledToolsets
161+
dynamic:=viper.GetBool("dynamic_toolsets")
162+
ifdynamic {
163+
// filter "all" from the enabled toolsets
164+
enabled=make([]string,0,len(cfg.enabledToolsets))
165+
for_,toolset:=rangecfg.enabledToolsets {
166+
iftoolset!="all" {
167+
enabled=append(enabled,toolset)
168+
}
169+
}
170+
}
171+
172+
// Create default toolsets
173+
toolsets,err:=github.InitToolsets(enabled,cfg.readOnly,getClient,t)
174+
context:=github.InitContextToolset(getClient,t)
175+
176+
iferr!=nil {
177+
stdlog.Fatal("Failed to initialize toolsets:",err)
178+
}
179+
180+
// Register resources with the server
181+
github.RegisterResources(ghServer,getClient,t)
182+
// Register the tools with the server
183+
toolsets.RegisterTools(ghServer)
184+
context.RegisterTools(ghServer)
185+
186+
ifdynamic {
187+
dynamic:=github.InitDynamicToolset(ghServer,toolsets,t)
188+
dynamic.RegisterTools(ghServer)
189+
}
190+
154191
stdioServer:=server.NewStdioServer(ghServer)
155192

156193
stdLogger:=stdlog.New(cfg.logger.Writer(),"stdioserver",0)

‎go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ require (
66
github.com/docker/dockerv28.0.4+incompatible
77
github.com/google/go-cmpv0.7.0
88
github.com/google/go-github/v69v69.2.0
9-
github.com/mark3labs/mcp-gov0.18.0
9+
github.com/mark3labs/mcp-gov0.20.1
1010
github.com/migueleliasweb/go-github-mockv1.1.0
1111
github.com/sirupsen/logrusv1.9.3
1212
github.com/spf13/cobrav1.9.1

‎go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
5757
github.com/kr/prettyv0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
5858
github.com/kr/textv0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
5959
github.com/kr/textv0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
60-
github.com/mark3labs/mcp-gov0.18.0 h1:YuhgIVjNlTG2ZOwmrkORWyPTp0dz1opPEqvsPtySXao=
61-
github.com/mark3labs/mcp-gov0.18.0/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE=
60+
github.com/mark3labs/mcp-gov0.20.1 h1:E1Bbx9K8d8kQmDZ1QHblM38c7UU2evQ2LlkANk1U/zw=
61+
github.com/mark3labs/mcp-gov0.20.1/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE=
6262
github.com/migueleliasweb/go-github-mockv1.1.0 h1:GKaOBPsrPGkAKgtfuWY8MclS1xR6MInkx1SexJucMwE=
6363
github.com/migueleliasweb/go-github-mockv1.1.0/go.mod h1:pYe/XlGs4BGMfRY4vmeixVsODHnVDDhJ9zoi0qzSMHc=
6464
github.com/moby/docker-image-specv1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=

‎pkg/github/context_tools.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
10+
"github.com/github/github-mcp-server/pkg/translations"
11+
"github.com/mark3labs/mcp-go/mcp"
12+
"github.com/mark3labs/mcp-go/server"
13+
)
14+
15+
// GetMe creates a tool to get details of the authenticated user.
16+
funcGetMe(getClientGetClientFn,t translations.TranslationHelperFunc) (tool mcp.Tool,handler server.ToolHandlerFunc) {
17+
returnmcp.NewTool("get_me",
18+
mcp.WithDescription(t("TOOL_GET_ME_DESCRIPTION","Get details of the authenticated GitHub user. Use this when a request include\"me\",\"my\"...")),
19+
mcp.WithString("reason",
20+
mcp.Description("Optional: reason the session was created"),
21+
),
22+
),
23+
func(ctx context.Context,_ mcp.CallToolRequest) (*mcp.CallToolResult,error) {
24+
client,err:=getClient(ctx)
25+
iferr!=nil {
26+
returnnil,fmt.Errorf("failed to get GitHub client: %w",err)
27+
}
28+
user,resp,err:=client.Users.Get(ctx,"")
29+
iferr!=nil {
30+
returnnil,fmt.Errorf("failed to get user: %w",err)
31+
}
32+
deferfunc() {_=resp.Body.Close() }()
33+
34+
ifresp.StatusCode!=http.StatusOK {
35+
body,err:=io.ReadAll(resp.Body)
36+
iferr!=nil {
37+
returnnil,fmt.Errorf("failed to read response body: %w",err)
38+
}
39+
returnmcp.NewToolResultError(fmt.Sprintf("failed to get user: %s",string(body))),nil
40+
}
41+
42+
r,err:=json.Marshal(user)
43+
iferr!=nil {
44+
returnnil,fmt.Errorf("failed to marshal user: %w",err)
45+
}
46+
47+
returnmcp.NewToolResultText(string(r)),nil
48+
}
49+
}

‎pkg/github/context_tools_test.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
"testing"
8+
"time"
9+
10+
"github.com/github/github-mcp-server/pkg/translations"
11+
"github.com/google/go-github/v69/github"
12+
"github.com/migueleliasweb/go-github-mock/src/mock"
13+
"github.com/stretchr/testify/assert"
14+
"github.com/stretchr/testify/require"
15+
)
16+
17+
funcTest_GetMe(t*testing.T) {
18+
// Verify tool definition
19+
mockClient:=github.NewClient(nil)
20+
tool,_:=GetMe(stubGetClientFn(mockClient),translations.NullTranslationHelper)
21+
22+
assert.Equal(t,"get_me",tool.Name)
23+
assert.NotEmpty(t,tool.Description)
24+
assert.Contains(t,tool.InputSchema.Properties,"reason")
25+
assert.Empty(t,tool.InputSchema.Required)// No required parameters
26+
27+
// Setup mock user response
28+
mockUser:=&github.User{
29+
Login:github.Ptr("testuser"),
30+
Name:github.Ptr("Test User"),
31+
Email:github.Ptr("test@example.com"),
32+
Bio:github.Ptr("GitHub user for testing"),
33+
Company:github.Ptr("Test Company"),
34+
Location:github.Ptr("Test Location"),
35+
HTMLURL:github.Ptr("https://github.com/testuser"),
36+
CreatedAt:&github.Timestamp{Time:time.Now().Add(-365*24*time.Hour)},
37+
Type:github.Ptr("User"),
38+
Plan:&github.Plan{
39+
Name:github.Ptr("pro"),
40+
},
41+
}
42+
43+
tests:= []struct {
44+
namestring
45+
mockedClient*http.Client
46+
requestArgsmap[string]interface{}
47+
expectErrorbool
48+
expectedUser*github.User
49+
expectedErrMsgstring
50+
}{
51+
{
52+
name:"successful get user",
53+
mockedClient:mock.NewMockedHTTPClient(
54+
mock.WithRequestMatch(
55+
mock.GetUser,
56+
mockUser,
57+
),
58+
),
59+
requestArgs:map[string]interface{}{},
60+
expectError:false,
61+
expectedUser:mockUser,
62+
},
63+
{
64+
name:"successful get user with reason",
65+
mockedClient:mock.NewMockedHTTPClient(
66+
mock.WithRequestMatch(
67+
mock.GetUser,
68+
mockUser,
69+
),
70+
),
71+
requestArgs:map[string]interface{}{
72+
"reason":"Testing API",
73+
},
74+
expectError:false,
75+
expectedUser:mockUser,
76+
},
77+
{
78+
name:"get user fails",
79+
mockedClient:mock.NewMockedHTTPClient(
80+
mock.WithRequestMatchHandler(
81+
mock.GetUser,
82+
http.HandlerFunc(func(w http.ResponseWriter,_*http.Request) {
83+
w.WriteHeader(http.StatusUnauthorized)
84+
_,_=w.Write([]byte(`{"message": "Unauthorized"}`))
85+
}),
86+
),
87+
),
88+
requestArgs:map[string]interface{}{},
89+
expectError:true,
90+
expectedErrMsg:"failed to get user",
91+
},
92+
}
93+
94+
for_,tc:=rangetests {
95+
t.Run(tc.name,func(t*testing.T) {
96+
// Setup client with mock
97+
client:=github.NewClient(tc.mockedClient)
98+
_,handler:=GetMe(stubGetClientFn(client),translations.NullTranslationHelper)
99+
100+
// Create call request
101+
request:=createMCPRequest(tc.requestArgs)
102+
103+
// Call handler
104+
result,err:=handler(context.Background(),request)
105+
106+
// Verify results
107+
iftc.expectError {
108+
require.Error(t,err)
109+
assert.Contains(t,err.Error(),tc.expectedErrMsg)
110+
return
111+
}
112+
113+
require.NoError(t,err)
114+
115+
// Parse result and get text content if no error
116+
textContent:=getTextResult(t,result)
117+
118+
// Unmarshal and verify the result
119+
varreturnedUser github.User
120+
err=json.Unmarshal([]byte(textContent.Text),&returnedUser)
121+
require.NoError(t,err)
122+
123+
// Verify user details
124+
assert.Equal(t,*tc.expectedUser.Login,*returnedUser.Login)
125+
assert.Equal(t,*tc.expectedUser.Name,*returnedUser.Name)
126+
assert.Equal(t,*tc.expectedUser.Email,*returnedUser.Email)
127+
assert.Equal(t,*tc.expectedUser.Bio,*returnedUser.Bio)
128+
assert.Equal(t,*tc.expectedUser.HTMLURL,*returnedUser.HTMLURL)
129+
assert.Equal(t,*tc.expectedUser.Type,*returnedUser.Type)
130+
})
131+
}
132+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp