Skip to content
Draft
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
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ FROM gcr.io/distroless/base-debian12
# Add required MCP server annotation
LABEL io.modelcontextprotocol.server.name="io.github.github/github-mcp-server"

# Expose port 8080 for HTTP mode
EXPOSE 8080

# Set the working directory
WORKDIR /server
# Copy the binary from the build stage
Expand Down
209 changes: 209 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,215 @@ the hostname for GitHub Enterprise Server or GitHub Enterprise Cloud with data r
}
```

### HTTP Server Mode

The GitHub MCP Server supports HTTP mode for serving multiple concurrent clients with per-request authentication. This is ideal for enterprise deployments where a centralized MCP server serves multiple users or applications.

#### Starting the HTTP Server

Start the HTTP server with the `http` command:

```bash
# Start HTTP server on default port (8080)
github-mcp-server http

# Start HTTP server on custom port
github-mcp-server http --port 3000

# With Docker
docker run -p 8080:8080 ghcr.io/github/github-mcp-server http

# With Docker on custom port
docker run -p 3000:3000 ghcr.io/github/github-mcp-server http --port 3000
```

> **Note:** Unlike stdio mode, HTTP mode does not require a `GITHUB_PERSONAL_ACCESS_TOKEN` environment variable at startup. Instead, each client provides their token via the `Authorization` header.
#### Authentication with Authorization Header

Clients authenticate by including their GitHub Personal Access Token in the `Authorization` header of each request:

```
Authorization: Bearer ghp_your_github_token_here
```

This "Bring Your Own Token" (BYOT) approach enables:
- **Multi-tenancy**: Different users can use their own tokens with proper permissions
- **Security**: Tokens are never stored on the server
- **Flexibility**: Users can revoke/rotate tokens independently

#### Client Configuration Examples

##### VS Code with GitHub Copilot

Configure VS Code to connect to your HTTP server by adding the following to your VS Code MCP settings (`.vscode/settings.json` or user settings):

```json
{
"servers": {
"github-http": {
"type": "http",
"url": "http://your-mcp-server.example.com:8080",
"headers": {
"Authorization": "Bearer ${input:github_token}"
}
}
},
"inputs": [
{
"type": "promptString",
"id": "github_token",
"description": "GitHub Personal Access Token",
"password": true
}
]
}
```

VS Code will prompt for the `github_token` input when connecting.



> **Security Note:** When using hardcoded tokens in configuration files, ensure proper file permissions (e.g., `chmod 600`) to protect your token.
##### Other MCP Clients

For other MCP clients that support HTTP transport, ensure they:
1. Connect to the server's HTTP endpoint (e.g., `http://localhost:8080`)
2. Include the `Authorization: Bearer <token>` header in all requests
3. Use the MCP streamable HTTP transport protocol

Example with curl for testing:

```bash
# Test server health (this should fail without proper MCP request structure)
curl -H "Authorization: Bearer ghp_your_token" http://localhost:8080

# Proper MCP client implementation required for actual tool calls
```

#### Docker Deployment

##### Basic HTTP Server

Run the HTTP server in Docker with port mapping:

```bash
docker run -d \
--name github-mcp-http \
-p 8080:8080 \
ghcr.io/github/github-mcp-server http
```

##### With Logging

Enable file logging for debugging:

```bash
docker run -d \
--name github-mcp-http \
-p 8080:8080 \
-v $(pwd)/logs:/logs \
ghcr.io/github/github-mcp-server http --log-file /logs/server.log
```

##### With Custom Configuration

Use additional flags for configuration:

```bash
docker run -d \
--name github-mcp-http \
-p 8080:8080 \
ghcr.io/github/github-mcp-server http \
--port 8080 \
--toolsets actions,issues,pull_requests \
--read-only \
--log-file /var/log/github-mcp.log
```

##### Production Deployment with Docker Compose

Create a `docker-compose.yml` file:

```yaml
version: '3.8'
services:
github-mcp-server:
image: ghcr.io/github/github-mcp-server
command: http --port 8080 --log-file /logs/server.log
ports:
- "8080:8080"
volumes:
- ./logs:/logs
restart: unless-stopped
# Configure health checks at your load balancer or orchestrator level.
```
Then start with:
```bash
docker-compose up -d
```

#### GitHub Enterprise Support

HTTP mode works with GitHub Enterprise Server and GitHub Enterprise Cloud with data residency:

```bash
# GitHub Enterprise Server
docker run -d \
-p 8080:8080 \
ghcr.io/github/github-mcp-server http \
--gh-host https://github.yourcompany.com \
--port 8080

# GitHub Enterprise Cloud with data residency
docker run -d \
-p 8080:8080 \
ghcr.io/github/github-mcp-server http \
--gh-host https://octocorp.ghe.com \
--port 8080
```

Clients still provide their tokens via the `Authorization` header.

#### Security Considerations

When deploying the HTTP server:

1. **Use HTTPS in Production**: Always use a reverse proxy (nginx, Caddy, etc.) to terminate TLS
2. **Network Security**:
- Bind to localhost (`127.0.0.1`) for local-only access
- Use firewalls to restrict access to trusted networks
- Consider VPN or IP allowlisting for remote deployments
3. **Token Management**:
- Tokens are validated per-request and never stored
- Use fine-grained tokens with minimum required permissions
- Rotate tokens regularly
4. **Rate Limiting**: Consider adding rate limiting at the reverse proxy level
5. **Monitoring**: Enable logging to track usage and potential security issues

#### Troubleshooting HTTP Mode

**Server won't start:**
- Check if port 8080 (or your custom port) is already in use
- Ensure Docker port mapping is correct (`-p host_port:container_port`)

**Client connection fails:**
- Verify the server is running: `curl http://localhost:8080` (should return an error but connect)
- Check firewall rules allow connections to the port
- Verify the URL in client configuration matches the server address

**Authentication errors:**
- Ensure the `Authorization` header is properly formatted: `Bearer <token>`
- Verify the GitHub token is valid and not expired
- Check token has required permissions for the operations being performed

**Enable debug logging:**
```bash
github-mcp-server http --log-file debug.log
# Or with Docker:
docker run -p 8080:8080 -v $(pwd):/logs \
ghcr.io/github/github-mcp-server http --log-file /logs/debug.log
```

## Installation

### Install in GitHub Copilot on VS Code
Expand Down
54 changes: 54 additions & 0 deletions cmd/github-mcp-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,55 @@ var (
return ghmcp.RunStdioServer(stdioServerConfig)
},
}

httpCmd = &cobra.Command{
Use: "http",
Short: "Start HTTP server",
Long: `Start an HTTP server that supports multiple concurrent clients with per-request authentication.`,
RunE: func(_ *cobra.Command, _ []string) error {
// Parse toolsets
var enabledToolsets []string
if viper.IsSet("toolsets") {
if err := viper.UnmarshalKey("toolsets", &enabledToolsets); err != nil {
return fmt.Errorf("failed to unmarshal toolsets: %w", err)
}
}

// Parse tools
var enabledTools []string
if viper.IsSet("tools") {
if err := viper.UnmarshalKey("tools", &enabledTools); err != nil {
return fmt.Errorf("failed to unmarshal tools: %w", err)
}
}

// Parse enabled features
var enabledFeatures []string
if viper.IsSet("features") {
if err := viper.UnmarshalKey("features", &enabledFeatures); err != nil {
return fmt.Errorf("failed to unmarshal features: %w", err)
}
}

ttl := viper.GetDuration("repo-access-cache-ttl")
httpServerConfig := ghmcp.HTTPServerConfig{
Version: version,
Host: viper.GetString("host"),
Port: viper.GetInt("port"),
EnabledToolsets: enabledToolsets,
EnabledTools: enabledTools,
EnabledFeatures: enabledFeatures,
DynamicToolsets: viper.GetBool("dynamic_toolsets"),
ReadOnly: viper.GetBool("read-only"),
LogFilePath: viper.GetString("log-file"),
ContentWindowSize: viper.GetInt("content-window-size"),
LockdownMode: viper.GetBool("lockdown-mode"),
InsiderMode: viper.GetBool("insider-mode"),
RepoAccessCacheTTL: &ttl,
}
return ghmcp.RunHTTPServer(httpServerConfig)
},
}
)

func init() {
Expand Down Expand Up @@ -127,8 +176,13 @@ func init() {
_ = viper.BindPFlag("insider-mode", rootCmd.PersistentFlags().Lookup("insider-mode"))
_ = viper.BindPFlag("repo-access-cache-ttl", rootCmd.PersistentFlags().Lookup("repo-access-cache-ttl"))

// Add HTTP-specific flags
httpCmd.Flags().Int("port", 8080, "Port to listen on for HTTP server")
_ = viper.BindPFlag("port", httpCmd.Flags().Lookup("port"))

// Add subcommands
rootCmd.AddCommand(stdioCmd)
rootCmd.AddCommand(httpCmd)
}

func initConfig() {
Expand Down
3 changes: 0 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module github.com/github/github-mcp-server
go 1.24.0

require (
github.com/fatih/color v1.18.0
github.com/google/go-github/v79 v79.0.0
github.com/google/jsonschema-go v0.4.2
github.com/josephburnett/jd v1.9.2
Expand All @@ -21,8 +20,6 @@ require (
github.com/gorilla/css v1.0.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
Expand Down
9 changes: 0 additions & 9 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
Expand Down Expand Up @@ -53,11 +51,6 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/modelcontextprotocol/go-sdk v1.2.0 h1:Y23co09300CEk8iZ/tMxIX1dVmKZkzoSBZOpJwUnc/s=
Expand Down Expand Up @@ -130,9 +123,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
Expand Down
Loading