Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,70 @@ If you don't have Docker, you can use `go` to build the binary in the
command with the `GITHUB_PERSONAL_ACCESS_TOKEN` environment variable set to
your token.

## Filtering Tools

By default, the GitHub MCP Server exposes all available tools to the connected agent.
You can customize which tools are available using the `--include-tools` and `--exclude-tools` command-line arguments.

- `--include-tools`: Specifies a comma-separated list of tool names to include. Only these tools will be available. All other tools will be excluded.
- `--exclude-tools`: Specifies a comma-separated list of tool names to exclude. All other tools will be available.

These flags are mutually exclusive; you cannot use both at the same time.

**Examples:**

Here are two ways you might configure the server in your `mcp.json`:

1. Include only specific tools (allowlist):
```json
{
"mcpServers": {
"github-with-include-tools": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"GITHUB_PERSONAL_ACCESS_TOKEN",
"ghcr.io/github/github-mcp-server",
"--include-tools",
"update_issue,create_issue"
],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "<YOUR_TOKEN>"
}
}
}
}
```

2. Exclude specific tools (denylist):
```json
{
"mcpServers": {
"github-with-include-tools": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"GITHUB_PERSONAL_ACCESS_TOKEN",
"ghcr.io/github/github-mcp-server",
"--exclude-tools",
"get_file_contents,get_issue,get_issue_comments,get_me,get_pull_request,get_pull_request_comments,get_pull_request_files,get_pull_request_reviews,get_pull_request_status,list_code_scanning_alerts,list_commits,list_issues,list_pull_requests"
],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "<YOUR_TOKEN>"
}
}
}
}
```

Using these configuration options allows you to precisely control the capabilities granted to the AI agent connecting to the server, enhancing security and alignment with intended use cases.

## GitHub Enterprise Server

The flag `--gh-host` and the environment variable `GH_HOST` can be used to set
Expand Down
22 changes: 20 additions & 2 deletions cmd/github-mcp-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ var (
readOnly := viper.GetBool("read-only")
exportTranslations := viper.GetBool("export-translations")
prettyPrintJSON := viper.GetBool("pretty-print-json")

// Get tool filtering configuration
excludeToolsRaw := viper.GetString("exclude-tools")
includeToolsRaw := viper.GetString("include-tools")

// Validate that only one of include/exclude is provided
if excludeToolsRaw != "" && includeToolsRaw != "" {
stdlog.Fatal("Error: --include-tools and --exclude-tools flags cannot be used together.")
}

logger, err := initLogger(logFile)
if err != nil {
stdlog.Fatal("Failed to initialize logger:", err)
Expand All @@ -53,6 +63,8 @@ var (
logCommands: logCommands,
exportTranslations: exportTranslations,
prettyPrintJSON: prettyPrintJSON,
excludeTools: excludeToolsRaw, // Pass raw strings
includeTools: includeToolsRaw, // Pass raw strings
}
if err := runStdioServer(cfg); err != nil {
stdlog.Fatal("failed to run stdio server:", err)
Expand All @@ -71,6 +83,8 @@ func init() {
rootCmd.PersistentFlags().Bool("export-translations", false, "Save translations to a JSON file")
rootCmd.PersistentFlags().String("gh-host", "", "Specify the GitHub hostname (for GitHub Enterprise etc.)")
rootCmd.PersistentFlags().Bool("pretty-print-json", false, "Pretty print JSON output")
rootCmd.PersistentFlags().String("exclude-tools", "", "Comma-separated list of tools to exclude from the server")
rootCmd.PersistentFlags().String("include-tools", "", "Comma-separated list of tools to include in the server (only these tools will be exposed)")

// Bind flag to viper
_ = viper.BindPFlag("read-only", rootCmd.PersistentFlags().Lookup("read-only"))
Expand All @@ -79,6 +93,8 @@ func init() {
_ = viper.BindPFlag("export-translations", rootCmd.PersistentFlags().Lookup("export-translations"))
_ = viper.BindPFlag("gh-host", rootCmd.PersistentFlags().Lookup("gh-host"))
_ = viper.BindPFlag("pretty-print-json", rootCmd.PersistentFlags().Lookup("pretty-print-json"))
_ = viper.BindPFlag("exclude-tools", rootCmd.PersistentFlags().Lookup("exclude-tools"))
_ = viper.BindPFlag("include-tools", rootCmd.PersistentFlags().Lookup("include-tools"))

// Add subcommands
rootCmd.AddCommand(stdioCmd)
Expand Down Expand Up @@ -113,6 +129,8 @@ type runConfig struct {
logCommands bool
exportTranslations bool
prettyPrintJSON bool
excludeTools string
includeTools string
}

// JSONPrettyPrintWriter is a Writer that pretty prints input to indented JSON
Expand Down Expand Up @@ -157,8 +175,8 @@ func runStdioServer(cfg runConfig) error {

t, dumpTranslations := translations.TranslationHelper()

// Create
ghServer := github.NewServer(ghClient, cfg.readOnly, t)
// Create server with tool filtering
ghServer := github.NewServer(ghClient, cfg.readOnly, t, cfg.excludeTools, cfg.includeTools)
stdioServer := server.NewStdioServer(ghServer)

stdLogger := stdlog.New(cfg.logger.Writer(), "stdioserver", 0)
Expand Down
96 changes: 65 additions & 31 deletions pkg/github/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"io"
"net/http"
"strings"

"github.com/github/github-mcp-server/pkg/translations"
"github.com/google/go-github/v69/github"
Expand All @@ -15,14 +16,46 @@ import (
)

// NewServer creates a new GitHub MCP server with the specified GH client and logger.
func NewServer(client *github.Client, readOnly bool, t translations.TranslationHelperFunc) *server.MCPServer {
func NewServer(client *github.Client, readOnly bool, t translations.TranslationHelperFunc, excludeTools string, includeTools string) *server.MCPServer {
// Create a new MCP server
s := server.NewMCPServer(
"github-mcp-server",
"0.0.1",
server.WithResourceCapabilities(true, true),
server.WithLogging())

// Parse tool lists
excludeList := make(map[string]bool)
if excludeTools != "" {
for _, tool := range strings.Split(excludeTools, ",") {
excludeList[strings.TrimSpace(tool)] = true
}
}

includeList := make(map[string]bool)
if includeTools != "" {
for _, tool := range strings.Split(includeTools, ",") {
includeList[strings.TrimSpace(tool)] = true
}
}

// Helper function to check if a tool should be included
shouldIncludeTool := func(toolName string) bool {
// If include list is not empty, only include tools in that list
if len(includeList) > 0 {
return includeList[toolName]
}
// Otherwise, include all tools except those in the exclude list
return !excludeList[toolName]
}

// Helper function to add a tool if it should be included
addToolIfIncluded := func(tool mcp.Tool, handler server.ToolHandlerFunc) {
if shouldIncludeTool(tool.Name) {
s.AddTool(tool, handler)
}
}

// Add GitHub Resources
s.AddResourceTemplate(getRepositoryResourceContent(client, t))
s.AddResourceTemplate(getRepositoryResourceBranchContent(client, t))
Expand All @@ -31,52 +64,53 @@ func NewServer(client *github.Client, readOnly bool, t translations.TranslationH
s.AddResourceTemplate(getRepositoryResourcePrContent(client, t))

// Add GitHub tools - Issues
s.AddTool(getIssue(client, t))
s.AddTool(searchIssues(client, t))
s.AddTool(listIssues(client, t))
addToolIfIncluded(getIssue(client, t))
addToolIfIncluded(searchIssues(client, t))
addToolIfIncluded(listIssues(client, t))

if !readOnly {
s.AddTool(createIssue(client, t))
s.AddTool(addIssueComment(client, t))
s.AddTool(createIssue(client, t))
s.AddTool(updateIssue(client, t))
addToolIfIncluded(createIssue(client, t))
addToolIfIncluded(addIssueComment(client, t))
addToolIfIncluded(updateIssue(client, t))
}

// Add GitHub tools - Pull Requests
s.AddTool(getPullRequest(client, t))
s.AddTool(listPullRequests(client, t))
s.AddTool(getPullRequestFiles(client, t))
s.AddTool(getPullRequestStatus(client, t))
s.AddTool(getPullRequestComments(client, t))
s.AddTool(getPullRequestReviews(client, t))
addToolIfIncluded(getPullRequest(client, t))
addToolIfIncluded(listPullRequests(client, t))
addToolIfIncluded(getPullRequestFiles(client, t))
addToolIfIncluded(getPullRequestStatus(client, t))
addToolIfIncluded(getPullRequestComments(client, t))
addToolIfIncluded(getPullRequestReviews(client, t))
if !readOnly {
s.AddTool(mergePullRequest(client, t))
s.AddTool(updatePullRequestBranch(client, t))
s.AddTool(createPullRequestReview(client, t))
s.AddTool(createPullRequest(client, t))
addToolIfIncluded(mergePullRequest(client, t))
addToolIfIncluded(updatePullRequestBranch(client, t))
addToolIfIncluded(createPullRequestReview(client, t))
addToolIfIncluded(createPullRequest(client, t))
}

// Add GitHub tools - Repositories
s.AddTool(searchRepositories(client, t))
s.AddTool(getFileContents(client, t))
s.AddTool(listCommits(client, t))
addToolIfIncluded(searchRepositories(client, t))
addToolIfIncluded(getFileContents(client, t))
addToolIfIncluded(listCommits(client, t))
if !readOnly {
s.AddTool(createOrUpdateFile(client, t))
s.AddTool(createRepository(client, t))
s.AddTool(forkRepository(client, t))
s.AddTool(createBranch(client, t))
s.AddTool(pushFiles(client, t))
addToolIfIncluded(createOrUpdateFile(client, t))
addToolIfIncluded(createRepository(client, t))
addToolIfIncluded(forkRepository(client, t))
addToolIfIncluded(createBranch(client, t))
addToolIfIncluded(pushFiles(client, t))
}

// Add GitHub tools - Search
s.AddTool(searchCode(client, t))
s.AddTool(searchUsers(client, t))
addToolIfIncluded(searchCode(client, t))
addToolIfIncluded(searchUsers(client, t))

// Add GitHub tools - Users
s.AddTool(getMe(client, t))
addToolIfIncluded(getMe(client, t))

// Add GitHub tools - Code Scanning
s.AddTool(getCodeScanningAlert(client, t))
s.AddTool(listCodeScanningAlerts(client, t))
addToolIfIncluded(getCodeScanningAlert(client, t))
addToolIfIncluded(listCodeScanningAlerts(client, t))

return s
}

Expand Down