github/github-mcp-server · error
starting callback listener on %s: %w
Error message
starting callback listener on %s: %w
What it means
net.Listen('tcp', addr) failed for the local OAuth callback listener — the TCP port is already bound (or cannot be bound) on the chosen interface. addr is '127.0.0.1:PORT' for native runs or '0.0.0.0:PORT' inside Docker (listenCallback at internal/oauth/callback.go:44-55). The %w wrap keeps the OS error: 'address already in use' or 'permission denied'.
Source
Thrown at internal/oauth/callback.go:53
}
// listenCallback binds the local callback listener.
//
// It binds to loopback (127.0.0.1) by default so the callback server is never
// exposed on other interfaces. bindAll is set only inside a container, where
// Docker's published-port DNAT delivers traffic to the container's eth0 rather
// than to loopback; host-side exposure is still constrained by the publish
// (e.g. -p 127.0.0.1:8085:8085). A native run — even with a fixed port — stays
// on loopback.
func listenCallback(port int, bindAll bool) (net.Listener, error) {
host := "127.0.0.1"
if bindAll {
host = "0.0.0.0"
}
addr := fmt.Sprintf("%s:%d", host, port)
listener, err := net.Listen("tcp", addr)
if err != nil {
return nil, fmt.Errorf("starting callback listener on %s: %w", addr, err)
}
return listener, nil
}
// newCallbackServer starts a callback server on listener that validates state
// and reports the result on a buffered channel. The redirect URI always uses
// localhost so it matches the value registered on the OAuth/GitHub App.
func newCallbackServer(listener net.Listener, expectedState string) *callbackServer {
cs := &callbackServer{
server: &http.Server{ReadHeaderTimeout: 10 * time.Second}, // ReadHeaderTimeout guards against Slowloris.
listener: listener,
redirect: fmt.Sprintf("http://localhost:%d/callback", listener.Addr().(*net.TCPAddr).Port),
results: make(chan callbackResult, 1),
}
cs.server.Handler = cs.handler(expectedState)
go func() {
if err := cs.server.Serve(listener); err != nil && err != http.ErrServerClosed {View on GitHub (pinned to 0ea1f775a7)
Solutions
- Find and stop the holder: lsof -nP -iTCP:PORT -sTCP:LISTEN (macOS/Linux) or ss -ltnp 'sport = :PORT' (Linux)
- Set a different --oauth-callback-port (and update the callback URL registered on the OAuth/GitHub App)
- If the port must be <1024, run with CAP_NET_BIND_SERVICE or pick an unprivileged port
- Kill orphaned instances of the server before starting a new one
Example fix
// before: second instance with same fixed port $ github-mcp-server --oauth-callback-port 8085 // error: starting callback listener on 127.0.0.1:8085: listen tcp 127.0.0.1:8085: bind: address already in use // after: free the port or pick another $ lsof -tiTCP:8085 -sTCP:LISTEN | xargs kill $ github-mcp-server --oauth-callback-port 8086
Defensive patterns
Strategy: validation
Validate before calling
func portFree(host string, port int) bool {
ln, err := net.Listen("tcp", fmt.Sprintf("%s:%d", host, port))
if err != nil { return false }
_ = ln.Close()
return true
}
// before starting the flow:
if cfg.CallbackPort != 0 && !portFree("127.0.0.1", cfg.CallbackPort) {
return fmt.Errorf("callback port %d busy", cfg.CallbackPort)
} Try / catch
if strings.Contains(err.Error(), "address already in use") {
// holder conflict: identify via lsof/ss, then stop it or change the port
} Prevention
- Pre-bind-check the fixed callback port at startup
- Ensure process managers fully stop old instances (KillMode=control-group for systemd) before starting new ones
- Choose an unprivileged, rarely-used port and document it as reserved for this server
When it happens
Trigger: Another process holds the configured CallbackPort (a previous server instance that did not exit, a second copy of the MCP server); port <1024 requested on Linux without CAP_NET_BIND_SERVICE ('permission denied'); inside Docker, another container in the same network namespace already bound 0.0.0.0:PORT.
Common situations: Two MCP server instances configured with the same --oauth-callback-port; a stale process from a crashed session; a dev server coincidentally on the same port; running with a port that requires root; the port registered on the OAuth app being taken by another tool on a shared workstation.
Related errors
- OAuth callback port %d is not available; another process may
- %w: %w
- requesting device code: %w
- failed to create OAuth handler: %w
- HTTP server error: %w
AI-assisted analysis of github/github-mcp-server@0ea1f775a7 (2026-08-15).
Data as JSON: /api/errors/64087ec862dc3c50.
Report an issue: GitHub.