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

Commit8d56fe2

Browse files
feat: partition tools by product/feature
1 parent01aefd3 commit8d56fe2

File tree

11 files changed

+1328
-227
lines changed

11 files changed

+1328
-227
lines changed

‎README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,79 @@ If you don't have Docker, you can use `go` to build the binary in the
9696
command with the`GITHUB_PERSONAL_ACCESS_TOKEN` environment variable set to
9797
your token.
9898

99+
##Tool Configuration
100+
101+
The GitHub MCP Server supports enabling or disabling specific groups of functionalities via the`--toolsets` flag. This allows you to control which GitHub API capabilities are available to your AI tools.
102+
103+
###Available Toolsets
104+
105+
The following sets of tools are available:
106+
107+
| Toolset| Description| Default Status|
108+
| -----------------------| -------------------------------------------------------------| --------------|
109+
|`repos`| Repository-related tools (file operations, branches, commits)| Enabled|
110+
|`issues`| Issue-related tools (create, read, update, comment)| Enabled|
111+
|`search`| Search functionality (code, repositories, users)| Enabled|
112+
|`pull_requests`| Pull request operations (create, merge, review)| Enabled|
113+
|`context`| Tools providing context about current user and GitHub context| Enabled|
114+
|`dynamic`| Tool discovery and dynamic enablement of GitHub MCP tools| Enabled|
115+
|`code_security`| Code scanning alerts and security features| Disabled|
116+
|`experiments`| Experimental features (not considered stable)| Disabled|
117+
|`all`| Special flag to enable all features| Disabled|
118+
119+
###Specifying Toolsets
120+
121+
You can enable specific features in two ways:
122+
123+
1.**Using Command Line Argument**:
124+
125+
```bash
126+
github-mcp-server --toolsets repos,issues,pull_requests,code_security
127+
```
128+
129+
2.**Using Environment Variable**:
130+
```bash
131+
GITHUB_TOOLSETS="repos,issues,pull_requests,code_security" ./github-mcp-server
132+
```
133+
134+
The environment variable`GITHUB_TOOLSETS` takes precedence over the command line argument if both are provided.
135+
136+
###Default Enabled Toolsets
137+
138+
By default, the following toolsets are enabled:
139+
140+
-`repos`
141+
-`issues`
142+
-`pull_requests`
143+
-`search`
144+
-`context-_ools`
145+
-`dynamic_tools`
146+
147+
###Using With Docker
148+
149+
When using Docker, you can pass the toolsets as environment variables:
150+
151+
```bash
152+
docker run -i --rm \
153+
-e GITHUB_PERSONAL_ACCESS_TOKEN=<your-token> \
154+
-e GITHUB_TOOLSETS="repos,issues,pull_requests,code_security,experiments" \
155+
ghcr.io/github/github-mcp-server
156+
```
157+
158+
###The "everything" Toolset
159+
160+
The special toolset`everything` can be provided to enable all available features regardless of any other toolsets passed:
161+
162+
```bash
163+
./github-mcp-server --toolsets everything
164+
```
165+
166+
Or using the environment variable:
167+
168+
```bash
169+
GITHUB_TOOLSETS="everything" ./github-mcp-server
170+
```
171+
99172
##GitHub Enterprise Server
100173

101174
The flag`--gh-host` and the environment variable`GH_HOST` can be used to set

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

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
stdlog"log"
88
"os"
99
"os/signal"
10+
"strings"
1011
"syscall"
1112

1213
"github.com/github/github-mcp-server/pkg/github"
@@ -43,12 +44,25 @@ var (
4344
iferr!=nil {
4445
stdlog.Fatal("Failed to initialize logger:",err)
4546
}
47+
48+
enabledToolsets:=viper.GetStringSlice("toolsets")
49+
50+
// Env gets precedence over command line flags
51+
ifenvToolsets:=os.Getenv("GITHUB_TOOLSETS");envToolsets!="" {
52+
enabledToolsets= []string{}
53+
// Split envFeats by comma, trim whitespace, and add to the slice
54+
for_,toolset:=rangestrings.Split(envToolsets,",") {
55+
enabledToolsets=append(enabledToolsets,strings.TrimSpace(toolset))
56+
}
57+
}
58+
4659
logCommands:=viper.GetBool("enable-command-logging")
4760
cfg:=runConfig{
4861
readOnly:readOnly,
4962
logger:logger,
5063
logCommands:logCommands,
5164
exportTranslations:exportTranslations,
65+
enabledToolsets:enabledToolsets,
5266
}
5367
iferr:=runStdioServer(cfg);err!=nil {
5468
stdlog.Fatal("failed to run stdio server:",err)
@@ -61,13 +75,15 @@ func init() {
6175
cobra.OnInitialize(initConfig)
6276

6377
// Add global flags that will be shared by all commands
78+
rootCmd.PersistentFlags().StringSlice("toolsets",github.DefaultTools,"A comma separated list of groups of tools to enable, defaults to issues/repos/search")
6479
rootCmd.PersistentFlags().Bool("read-only",false,"Restrict the server to read-only operations")
6580
rootCmd.PersistentFlags().String("log-file","","Path to log file")
6681
rootCmd.PersistentFlags().Bool("enable-command-logging",false,"When enabled, the server will log all command requests and responses to the log file")
6782
rootCmd.PersistentFlags().Bool("export-translations",false,"Save translations to a JSON file")
6883
rootCmd.PersistentFlags().String("gh-host","","Specify the GitHub hostname (for GitHub Enterprise etc.)")
6984

7085
// Bind flag to viper
86+
_=viper.BindPFlag("toolsets",rootCmd.PersistentFlags().Lookup("toolsets"))
7187
_=viper.BindPFlag("read-only",rootCmd.PersistentFlags().Lookup("read-only"))
7288
_=viper.BindPFlag("log-file",rootCmd.PersistentFlags().Lookup("log-file"))
7389
_=viper.BindPFlag("enable-command-logging",rootCmd.PersistentFlags().Lookup("enable-command-logging"))
@@ -106,6 +122,7 @@ type runConfig struct {
106122
logger*log.Logger
107123
logCommandsbool
108124
exportTranslationsbool
125+
enabledToolsets []string
109126
}
110127

111128
funcrunStdioServer(cfgrunConfig)error {
@@ -140,8 +157,18 @@ func runStdioServer(cfg runConfig) error {
140157
getClient:=func(_ context.Context) (*gogithub.Client,error) {
141158
returnghClient,nil// closing over client
142159
}
143-
// Create
144-
ghServer:=github.NewServer(getClient,version,cfg.readOnly,t)
160+
161+
// Create server
162+
ghServer:=github.NewServer(version)
163+
164+
// Create toolsets
165+
toolsets,err:=github.InitToolsets(ghServer,cfg.enabledToolsets,cfg.readOnly,getClient,t)
166+
iferr!=nil {
167+
stdlog.Fatal("Failed to initialize toolsets:",err)
168+
}
169+
// Register the tools with the server
170+
toolsets.RegisterTools(ghServer)
171+
145172
stdioServer:=server.NewStdioServer(ghServer)
146173

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

‎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