Skip to content

Commit 10855ee

Browse files
authored
feat: http mode for centralized deployments
feat: http mode for centralized deployments
2 parents 15e66b3 + b28ab64 commit 10855ee

6 files changed

Lines changed: 453 additions & 12 deletions

File tree

Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ FROM gcr.io/distroless/base-debian12
2222
# Add required MCP server annotation
2323
LABEL io.modelcontextprotocol.server.name="io.github.github/github-mcp-server"
2424

25+
# Expose port 8080 for HTTP mode
26+
EXPOSE 8080
27+
2528
# Set the working directory
2629
WORKDIR /server
2730
# Copy the binary from the build stage

README.md

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,215 @@ the hostname for GitHub Enterprise Server or GitHub Enterprise Cloud with data r
266266
}
267267
```
268268

269+
### HTTP Server Mode
270+
271+
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.
272+
273+
#### Starting the HTTP Server
274+
275+
Start the HTTP server with the `http` command:
276+
277+
```bash
278+
# Start HTTP server on default port (8080)
279+
github-mcp-server http
280+
281+
# Start HTTP server on custom port
282+
github-mcp-server http --port 3000
283+
284+
# With Docker
285+
docker run -p 8080:8080 ghcr.io/github/github-mcp-server http
286+
287+
# With Docker on custom port
288+
docker run -p 3000:3000 ghcr.io/github/github-mcp-server http --port 3000
289+
```
290+
291+
> **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.
292+
#### Authentication with Authorization Header
293+
294+
Clients authenticate by including their GitHub Personal Access Token in the `Authorization` header of each request:
295+
296+
```
297+
Authorization: Bearer ghp_your_github_token_here
298+
```
299+
300+
This "Bring Your Own Token" (BYOT) approach enables:
301+
- **Multi-tenancy**: Different users can use their own tokens with proper permissions
302+
- **Security**: Tokens are never stored on the server
303+
- **Flexibility**: Users can revoke/rotate tokens independently
304+
305+
#### Client Configuration Examples
306+
307+
##### VS Code with GitHub Copilot
308+
309+
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):
310+
311+
```json
312+
{
313+
"servers": {
314+
"github-http": {
315+
"type": "http",
316+
"url": "http://your-mcp-server.example.com:8080",
317+
"headers": {
318+
"Authorization": "Bearer ${input:github_token}"
319+
}
320+
}
321+
},
322+
"inputs": [
323+
{
324+
"type": "promptString",
325+
"id": "github_token",
326+
"description": "GitHub Personal Access Token",
327+
"password": true
328+
}
329+
]
330+
}
331+
```
332+
333+
VS Code will prompt for the `github_token` input when connecting.
334+
335+
336+
337+
> **Security Note:** When using hardcoded tokens in configuration files, ensure proper file permissions (e.g., `chmod 600`) to protect your token.
338+
##### Other MCP Clients
339+
340+
For other MCP clients that support HTTP transport, ensure they:
341+
1. Connect to the server's HTTP endpoint (e.g., `http://localhost:8080`)
342+
2. Include the `Authorization: Bearer <token>` header in all requests
343+
3. Use the MCP streamable HTTP transport protocol
344+
345+
Example with curl for testing:
346+
347+
```bash
348+
# Test server health (this should fail without proper MCP request structure)
349+
curl -H "Authorization: Bearer ghp_your_token" http://localhost:8080
350+
351+
# Proper MCP client implementation required for actual tool calls
352+
```
353+
354+
#### Docker Deployment
355+
356+
##### Basic HTTP Server
357+
358+
Run the HTTP server in Docker with port mapping:
359+
360+
```bash
361+
docker run -d \
362+
--name github-mcp-http \
363+
-p 8080:8080 \
364+
ghcr.io/github/github-mcp-server http
365+
```
366+
367+
##### With Logging
368+
369+
Enable file logging for debugging:
370+
371+
```bash
372+
docker run -d \
373+
--name github-mcp-http \
374+
-p 8080:8080 \
375+
-v $(pwd)/logs:/logs \
376+
ghcr.io/github/github-mcp-server http --log-file /logs/server.log
377+
```
378+
379+
##### With Custom Configuration
380+
381+
Use additional flags for configuration:
382+
383+
```bash
384+
docker run -d \
385+
--name github-mcp-http \
386+
-p 8080:8080 \
387+
ghcr.io/github/github-mcp-server http \
388+
--port 8080 \
389+
--toolsets actions,issues,pull_requests \
390+
--read-only \
391+
--log-file /var/log/github-mcp.log
392+
```
393+
394+
##### Production Deployment with Docker Compose
395+
396+
Create a `docker-compose.yml` file:
397+
398+
```yaml
399+
version: '3.8'
400+
services:
401+
github-mcp-server:
402+
image: ghcr.io/github/github-mcp-server
403+
command: http --port 8080 --log-file /logs/server.log
404+
ports:
405+
- "8080:8080"
406+
volumes:
407+
- ./logs:/logs
408+
restart: unless-stopped
409+
# Configure health checks at your load balancer or orchestrator level.
410+
```
411+
Then start with:
412+
```bash
413+
docker-compose up -d
414+
```
415+
416+
#### GitHub Enterprise Support
417+
418+
HTTP mode works with GitHub Enterprise Server and GitHub Enterprise Cloud with data residency:
419+
420+
```bash
421+
# GitHub Enterprise Server
422+
docker run -d \
423+
-p 8080:8080 \
424+
ghcr.io/github/github-mcp-server http \
425+
--gh-host https://github.yourcompany.com \
426+
--port 8080
427+
428+
# GitHub Enterprise Cloud with data residency
429+
docker run -d \
430+
-p 8080:8080 \
431+
ghcr.io/github/github-mcp-server http \
432+
--gh-host https://octocorp.ghe.com \
433+
--port 8080
434+
```
435+
436+
Clients still provide their tokens via the `Authorization` header.
437+
438+
#### Security Considerations
439+
440+
When deploying the HTTP server:
441+
442+
1. **Use HTTPS in Production**: Always use a reverse proxy (nginx, Caddy, etc.) to terminate TLS
443+
2. **Network Security**:
444+
- Bind to localhost (`127.0.0.1`) for local-only access
445+
- Use firewalls to restrict access to trusted networks
446+
- Consider VPN or IP allowlisting for remote deployments
447+
3. **Token Management**:
448+
- Tokens are validated per-request and never stored
449+
- Use fine-grained tokens with minimum required permissions
450+
- Rotate tokens regularly
451+
4. **Rate Limiting**: Consider adding rate limiting at the reverse proxy level
452+
5. **Monitoring**: Enable logging to track usage and potential security issues
453+
454+
#### Troubleshooting HTTP Mode
455+
456+
**Server won't start:**
457+
- Check if port 8080 (or your custom port) is already in use
458+
- Ensure Docker port mapping is correct (`-p host_port:container_port`)
459+
460+
**Client connection fails:**
461+
- Verify the server is running: `curl http://localhost:8080` (should return an error but connect)
462+
- Check firewall rules allow connections to the port
463+
- Verify the URL in client configuration matches the server address
464+
465+
**Authentication errors:**
466+
- Ensure the `Authorization` header is properly formatted: `Bearer <token>`
467+
- Verify the GitHub token is valid and not expired
468+
- Check token has required permissions for the operations being performed
469+
470+
**Enable debug logging:**
471+
```bash
472+
github-mcp-server http --log-file debug.log
473+
# Or with Docker:
474+
docker run -p 8080:8080 -v $(pwd):/logs \
475+
ghcr.io/github/github-mcp-server http --log-file /logs/debug.log
476+
```
477+
269478
## Installation
270479

271480
### Install in GitHub Copilot on VS Code

cmd/github-mcp-server/main.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,55 @@ var (
8989
return ghmcp.RunStdioServer(stdioServerConfig)
9090
},
9191
}
92+
93+
httpCmd = &cobra.Command{
94+
Use: "http",
95+
Short: "Start HTTP server",
96+
Long: `Start an HTTP server that supports multiple concurrent clients with per-request authentication.`,
97+
RunE: func(_ *cobra.Command, _ []string) error {
98+
// Parse toolsets
99+
var enabledToolsets []string
100+
if viper.IsSet("toolsets") {
101+
if err := viper.UnmarshalKey("toolsets", &enabledToolsets); err != nil {
102+
return fmt.Errorf("failed to unmarshal toolsets: %w", err)
103+
}
104+
}
105+
106+
// Parse tools
107+
var enabledTools []string
108+
if viper.IsSet("tools") {
109+
if err := viper.UnmarshalKey("tools", &enabledTools); err != nil {
110+
return fmt.Errorf("failed to unmarshal tools: %w", err)
111+
}
112+
}
113+
114+
// Parse enabled features
115+
var enabledFeatures []string
116+
if viper.IsSet("features") {
117+
if err := viper.UnmarshalKey("features", &enabledFeatures); err != nil {
118+
return fmt.Errorf("failed to unmarshal features: %w", err)
119+
}
120+
}
121+
122+
ttl := viper.GetDuration("repo-access-cache-ttl")
123+
httpServerConfig := ghmcp.HTTPServerConfig{
124+
Version: version,
125+
Host: viper.GetString("host"),
126+
Port: viper.GetInt("port"),
127+
EnabledToolsets: enabledToolsets,
128+
EnabledTools: enabledTools,
129+
EnabledFeatures: enabledFeatures,
130+
DynamicToolsets: viper.GetBool("dynamic_toolsets"),
131+
ReadOnly: viper.GetBool("read-only"),
132+
LogFilePath: viper.GetString("log-file"),
133+
ContentWindowSize: viper.GetInt("content-window-size"),
134+
LockdownMode: viper.GetBool("lockdown-mode"),
135+
InsiderMode: viper.GetBool("insider-mode"),
136+
RepoAccessCacheTTL: &ttl,
137+
}
138+
return ghmcp.RunHTTPServer(httpServerConfig)
139+
},
140+
}
92141
)
93142

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

179+
// Add HTTP-specific flags
180+
httpCmd.Flags().Int("port", 8080, "Port to listen on for HTTP server")
181+
_ = viper.BindPFlag("port", httpCmd.Flags().Lookup("port"))
182+
130183
// Add subcommands
131184
rootCmd.AddCommand(stdioCmd)
185+
rootCmd.AddCommand(httpCmd)
132186
}
133187

134188
func initConfig() {

go.mod

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ module github.com/github/github-mcp-server
33
go 1.24.0
44

55
require (
6-
github.com/fatih/color v1.18.0
76
github.com/google/go-github/v79 v79.0.0
87
github.com/google/jsonschema-go v0.4.2
98
github.com/josephburnett/jd v1.9.2
@@ -21,8 +20,6 @@ require (
2120
github.com/gorilla/css v1.0.1 // indirect
2221
github.com/josharian/intern v1.0.0 // indirect
2322
github.com/mailru/easyjson v0.7.7 // indirect
24-
github.com/mattn/go-colorable v0.1.13 // indirect
25-
github.com/mattn/go-isatty v0.0.20 // indirect
2623
github.com/stretchr/objx v0.5.2 // indirect
2724
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
2825
go.yaml.in/yaml/v3 v3.0.4 // indirect

go.sum

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
66
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
77
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
88
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9-
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
10-
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
119
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
1210
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
1311
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
@@ -53,11 +51,6 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
5351
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
5452
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
5553
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
56-
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
57-
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
58-
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
59-
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
60-
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
6154
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
6255
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
6356
github.com/modelcontextprotocol/go-sdk v1.2.0 h1:Y23co09300CEk8iZ/tMxIX1dVmKZkzoSBZOpJwUnc/s=
@@ -130,9 +123,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
130123
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
131124
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
132125
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
133-
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
134126
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
135-
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
136127
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
137128
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
138129
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=

0 commit comments

Comments
 (0)