Skip to content

Commit 8aba085

Browse files
feat: partition tools by product/feature
1 parent 62eed34 commit 8aba085

17 files changed

+969
-257
lines changed

README.md

Lines changed: 92 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,95 @@ If you don't have Docker, you can use `go build` to build the binary in the
110110
}
111111
```
112112

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

115204
The flag `--gh-host` and the environment variable `GH_HOST` can be used to set
@@ -331,7 +420,6 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description
331420
### Repositories
332421

333422
- **create_or_update_file** - Create or update a single file in a repository
334-
335423
- `owner`: Repository owner (string, required)
336424
- `repo`: Repository name (string, required)
337425
- `path`: File path (string, required)
@@ -341,50 +429,43 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description
341429
- `sha`: File SHA if updating (string, optional)
342430

343431
- **list_branches** - List branches in a GitHub repository
344-
345432
- `owner`: Repository owner (string, required)
346433
- `repo`: Repository name (string, required)
347434
- `page`: Page number (number, optional)
348435
- `perPage`: Results per page (number, optional)
349436

350437
- **push_files** - Push multiple files in a single commit
351-
352438
- `owner`: Repository owner (string, required)
353439
- `repo`: Repository name (string, required)
354440
- `branch`: Branch to push to (string, required)
355441
- `files`: Files to push, each with path and content (array, required)
356442
- `message`: Commit message (string, required)
357443

358444
- **search_repositories** - Search for GitHub repositories
359-
360445
- `query`: Search query (string, required)
361446
- `sort`: Sort field (string, optional)
362447
- `order`: Sort order (string, optional)
363448
- `page`: Page number (number, optional)
364449
- `perPage`: Results per page (number, optional)
365450

366451
- **create_repository** - Create a new GitHub repository
367-
368452
- `name`: Repository name (string, required)
369453
- `description`: Repository description (string, optional)
370454
- `private`: Whether the repository is private (boolean, optional)
371455
- `autoInit`: Auto-initialize with README (boolean, optional)
372456

373457
- **get_file_contents** - Get contents of a file or directory
374-
375458
- `owner`: Repository owner (string, required)
376459
- `repo`: Repository name (string, required)
377460
- `path`: File path (string, required)
378461
- `ref`: Git reference (string, optional)
379462

380463
- **fork_repository** - Fork a repository
381-
382464
- `owner`: Repository owner (string, required)
383465
- `repo`: Repository name (string, required)
384466
- `organization`: Target organization name (string, optional)
385467

386468
- **create_branch** - Create a new branch
387-
388469
- `owner`: Repository owner (string, required)
389470
- `repo`: Repository name (string, required)
390471
- `branch`: New branch name (string, required)
@@ -405,16 +486,15 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description
405486
- `page`: Page number, for files in the commit (number, optional)
406487
- `perPage`: Results per page, for files in the commit (number, optional)
407488

408-
### Search
409-
410-
- **search_code** - Search for code across GitHub repositories
411-
489+
- **search_code** - Search for code across GitHub repositories
412490
- `query`: Search query (string, required)
413491
- `sort`: Sort field (string, optional)
414492
- `order`: Sort order (string, optional)
415493
- `page`: Page number (number, optional)
416494
- `perPage`: Results per page (number, optional)
417495

496+
### Users
497+
418498
- **search_users** - Search for GitHub users
419499
- `query`: Search query (string, required)
420500
- `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(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 & 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/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
5858
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
5959
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
60-
github.com/mark3labs/mcp-go v0.18.0 h1:YuhgIVjNlTG2ZOwmrkORWyPTp0dz1opPEqvsPtySXao=
61-
github.com/mark3labs/mcp-go v0.18.0/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE=
60+
github.com/mark3labs/mcp-go v0.20.1 h1:E1Bbx9K8d8kQmDZ1QHblM38c7UU2evQ2LlkANk1U/zw=
61+
github.com/mark3labs/mcp-go v0.20.1/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE=
6262
github.com/migueleliasweb/go-github-mock v1.1.0 h1:GKaOBPsrPGkAKgtfuWY8MclS1xR6MInkx1SexJucMwE=
6363
github.com/migueleliasweb/go-github-mock v1.1.0/go.mod h1:pYe/XlGs4BGMfRY4vmeixVsODHnVDDhJ9zoi0qzSMHc=
6464
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