Skip to content

Commit 45f55fc

Browse files
feat: partition tools by product/feature
1 parent bbba3bb commit 45f55fc

19 files changed

+1077
-255
lines changed

README.md

Lines changed: 92 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,95 @@ 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 (all are on by default):
106+
107+
| Toolset | Description |
108+
| ----------------------- | ------------------------------------------------------------- |
109+
| `repos` | Repository-related tools (file operations, branches, commits) |
110+
| `issues` | Issue-related tools (create, read, update, comment) |
111+
| `users ` | Anything relating to GitHub Users |
112+
| `pull_requests` | Pull request operations (create, merge, review) |
113+
| `code_security` | Code scanning alerts and security features |
114+
| `experiments` | Experimental features (not considered stable) |
115+
116+
#### Specifying Toolsets
117+
118+
To reduce the available tools, you can pass an allow-list in two ways:
119+
120+
1. **Using Command Line Argument**:
121+
122+
```bash
123+
github-mcp-server --toolsets repos,issues,pull_requests,code_security
124+
```
125+
126+
2. **Using Environment Variable**:
127+
```bash
128+
GITHUB_TOOLSETS="repos,issues,pull_requests,code_security" ./github-mcp-server
129+
```
130+
131+
The environment variable `GITHUB_TOOLSETS` takes precedence over the command line argument if both are provided.
132+
133+
Any toolsets you specify will be enabled from the start, including when `--dynamic-toolsets` is on.
134+
135+
You might want to do this if the model is confused about which tools to call and you only require a subset.
136+
137+
138+
### Using Toolsets With Docker
139+
140+
When using Docker, you can pass the toolsets as environment variables:
141+
142+
```bash
143+
docker run -i --rm \
144+
-e GITHUB_PERSONAL_ACCESS_TOKEN=<your-token> \
145+
-e GITHUB_TOOLSETS="repos,issues,pull_requests,code_security,experiments" \
146+
ghcr.io/github/github-mcp-server
147+
```
148+
149+
### The "all" Toolset
150+
151+
The special toolset `all` can be provided to enable all available toolsets regardless of any other configuration:
152+
153+
```bash
154+
./github-mcp-server --toolsets all
155+
```
156+
157+
Or using the environment variable:
158+
159+
```bash
160+
GITHUB_TOOLSETS="all" ./github-mcp-server
161+
```
162+
163+
## Dynamic Tool Discovery
164+
165+
Instead of starting with all tools enabled, you can turn on Dynamic Toolset Discovery.
166+
This feature provides tools that help the MCP Host application to discover and enable sets of GitHub tools only when needed.
167+
This helps to avoid situations where models get confused by the shear number of tools available to them, which varies by model.
168+
169+
### Using Dynamic Tool Discovery
170+
171+
When using the binary, you can pass the `--dynamic-toolsets` flag.
172+
173+
```bash
174+
./github-mcp-server --dynamic-toolsets
175+
```
176+
177+
When using Docker, you can pass the toolsets as environment variables:
178+
179+
```bash
180+
docker run -i --rm \
181+
-e GITHUB_PERSONAL_ACCESS_TOKEN=<your-token> \
182+
-e GITHUB_DYNAMIC_TOOLSETS=1 \
183+
ghcr.io/github/github-mcp-server
184+
```
185+
186+
187+
99188
## GitHub Enterprise Server
100189

101190
The flag `--gh-host` and the environment variable `GH_HOST` can be used to set
@@ -317,7 +406,6 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description
317406
### Repositories
318407

319408
- **create_or_update_file** - Create or update a single file in a repository
320-
321409
- `owner`: Repository owner (string, required)
322410
- `repo`: Repository name (string, required)
323411
- `path`: File path (string, required)
@@ -327,50 +415,43 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description
327415
- `sha`: File SHA if updating (string, optional)
328416

329417
- **list_branches** - List branches in a GitHub repository
330-
331418
- `owner`: Repository owner (string, required)
332419
- `repo`: Repository name (string, required)
333420
- `page`: Page number (number, optional)
334421
- `perPage`: Results per page (number, optional)
335422

336423
- **push_files** - Push multiple files in a single commit
337-
338424
- `owner`: Repository owner (string, required)
339425
- `repo`: Repository name (string, required)
340426
- `branch`: Branch to push to (string, required)
341427
- `files`: Files to push, each with path and content (array, required)
342428
- `message`: Commit message (string, required)
343429

344430
- **search_repositories** - Search for GitHub repositories
345-
346431
- `query`: Search query (string, required)
347432
- `sort`: Sort field (string, optional)
348433
- `order`: Sort order (string, optional)
349434
- `page`: Page number (number, optional)
350435
- `perPage`: Results per page (number, optional)
351436

352437
- **create_repository** - Create a new GitHub repository
353-
354438
- `name`: Repository name (string, required)
355439
- `description`: Repository description (string, optional)
356440
- `private`: Whether the repository is private (boolean, optional)
357441
- `autoInit`: Auto-initialize with README (boolean, optional)
358442

359443
- **get_file_contents** - Get contents of a file or directory
360-
361444
- `owner`: Repository owner (string, required)
362445
- `repo`: Repository name (string, required)
363446
- `path`: File path (string, required)
364447
- `ref`: Git reference (string, optional)
365448

366449
- **fork_repository** - Fork a repository
367-
368450
- `owner`: Repository owner (string, required)
369451
- `repo`: Repository name (string, required)
370452
- `organization`: Target organization name (string, optional)
371453

372454
- **create_branch** - Create a new branch
373-
374455
- `owner`: Repository owner (string, required)
375456
- `repo`: Repository name (string, required)
376457
- `branch`: New branch name (string, required)
@@ -391,16 +472,15 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description
391472
- `page`: Page number, for files in the commit (number, optional)
392473
- `perPage`: Results per page, for files in the commit (number, optional)
393474

394-
### Search
395-
396-
- **search_code** - Search for code across GitHub repositories
397-
475+
- **search_code** - Search for code across GitHub repositories
398476
- `query`: Search query (string, required)
399477
- `sort`: Sort field (string, optional)
400478
- `order`: Sort order (string, optional)
401479
- `page`: Page number (number, optional)
402480
- `perPage`: Results per page (number, optional)
403481

482+
### Users
483+
404484
- **search_users** - Search for GitHub users
405485
- `query`: Search query (string, required)
406486
- `sort`: Sort field (string, optional)

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
if err != 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
if err := 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
func initConfig() {
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
logCommands bool
109117
exportTranslations bool
118+
enabledToolsets []string
110119
}
111120

112121
func runStdioServer(cfg runConfig) error {
@@ -115,18 +124,14 @@ func runStdioServer(cfg runConfig) error {
115124
defer stop()
116125

117126
// Create GH client
118-
token := os.Getenv("GITHUB_PERSONAL_ACCESS_TOKEN")
127+
token := viper.GetString("personal_access_token")
119128
if token == "" {
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-
if host == "" {
128-
host = viper.GetString("gh-host")
129-
}
134+
host := viper.GetString("host")
130135

131136
if host != "" {
132137
var err error
@@ -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+
if dynamic {
163+
// filter "all" from the enabled toolsets
164+
enabled = make([]string, 0, len(cfg.enabledToolsets))
165+
for _, toolset := range cfg.enabledToolsets {
166+
if toolset != "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(ghServer, getClient, t)
175+
176+
if err != 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+
if dynamic {
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/docker v28.0.4+incompatible
77
github.com/google/go-cmp v0.7.0
88
github.com/google/go-github/v69 v69.2.0
9-
github.com/mark3labs/mcp-go v0.18.0
9+
github.com/mark3labs/mcp-go v0.20.1
1010
github.com/migueleliasweb/go-github-mock v1.1.0
1111
github.com/sirupsen/logrus v1.9.3
1212
github.com/spf13/cobra v1.9.1

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
5959
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
6060
github.com/mark3labs/mcp-go v0.18.0 h1:YuhgIVjNlTG2ZOwmrkORWyPTp0dz1opPEqvsPtySXao=
6161
github.com/mark3labs/mcp-go v0.18.0/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE=
62+
github.com/mark3labs/mcp-go v0.20.1 h1:E1Bbx9K8d8kQmDZ1QHblM38c7UU2evQ2LlkANk1U/zw=
63+
github.com/mark3labs/mcp-go v0.20.1/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE=
6264
github.com/migueleliasweb/go-github-mock v1.1.0 h1:GKaOBPsrPGkAKgtfuWY8MclS1xR6MInkx1SexJucMwE=
6365
github.com/migueleliasweb/go-github-mock v1.1.0/go.mod h1:pYe/XlGs4BGMfRY4vmeixVsODHnVDDhJ9zoi0qzSMHc=
6466
github.com/moby/docker-image-spec v1.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+
func GetMe(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
17+
return mcp.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+
if err != nil {
26+
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
27+
}
28+
user, resp, err := client.Users.Get(ctx, "")
29+
if err != nil {
30+
return nil, fmt.Errorf("failed to get user: %w", err)
31+
}
32+
defer func() { _ = resp.Body.Close() }()
33+
34+
if resp.StatusCode != http.StatusOK {
35+
body, err := io.ReadAll(resp.Body)
36+
if err != nil {
37+
return nil, fmt.Errorf("failed to read response body: %w", err)
38+
}
39+
return mcp.NewToolResultError(fmt.Sprintf("failed to get user: %s", string(body))), nil
40+
}
41+
42+
r, err := json.Marshal(user)
43+
if err != nil {
44+
return nil, fmt.Errorf("failed to marshal user: %w", err)
45+
}
46+
47+
return mcp.NewToolResultText(string(r)), nil
48+
}
49+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy