From 84ca9bb046cf062d44fb3b13cf61adc136667625 Mon Sep 17 00:00:00 2001 From: Anika Reiter Date: Fri, 17 Jul 2026 15:39:03 +0200 Subject: [PATCH] feat(http): add --authorization-server flag to override OAuth AS URL When deploying the MCP server behind an OAuth proxy (e.g. for GHES, which does not natively support RFC 8414, RFC 7591, or PKCE), the /.well-known/oauth-protected-resource endpoint currently always derives the authorization_servers URL from GITHUB_HOST. There is no way to point clients at a different authorization server without intercepting that endpoint at the ingress/proxy layer. The oauth.Config struct already has an AuthorizationServer field with the conditional logic in place (pkg/http/oauth/oauth.go), but it was never wired to any configuration surface. This commit exposes it as: - --authorization-server CLI flag on the http subcommand - GITHUB_AUTHORIZATION_SERVER environment variable (via viper's existing GITHUB_ prefix + automatic env mapping) When set, the value is passed through ServerConfig into oauth.Config, and the protected resource metadata advertises it directly instead of calling apiHost.AuthorizationServerURL(). --- cmd/github-mcp-server/main.go | 3 +++ docs/streamable-http.md | 25 +++++++++++++++++++++++++ pkg/http/server.go | 14 +++++++++++--- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/cmd/github-mcp-server/main.go b/cmd/github-mcp-server/main.go index 231b0cf2c3..7e82dce39d 100644 --- a/cmd/github-mcp-server/main.go +++ b/cmd/github-mcp-server/main.go @@ -180,6 +180,7 @@ var ( ListenHost: viper.GetString("listen-host"), BaseURL: viper.GetString("base-url"), ResourcePath: viper.GetString("base-path"), + AuthorizationServer: viper.GetString("authorization-server"), ExportTranslations: viper.GetBool("export-translations"), EnableCommandLogging: viper.GetBool("enable-command-logging"), LogFilePath: viper.GetString("log-file"), @@ -235,6 +236,7 @@ func init() { httpCmd.Flags().String("listen-host", "", "Host the HTTP server binds to (e.g. 127.0.0.1). Empty binds to all interfaces.") httpCmd.Flags().String("base-url", "", "Base URL where this server is publicly accessible (for OAuth resource metadata)") httpCmd.Flags().String("base-path", "", "Externally visible base path for the HTTP server (for OAuth resource metadata)") + httpCmd.Flags().String("authorization-server", "", "Override the authorization server URL in OAuth resource metadata. Useful when deploying behind an OAuth proxy (e.g. for GHES). Env: GITHUB_AUTHORIZATION_SERVER") httpCmd.Flags().Bool("scope-challenge", false, "Enable OAuth scope challenge responses") httpCmd.Flags().Bool("trust-proxy-headers", false, "Honor X-Forwarded-Host and X-Forwarded-Proto when constructing OAuth resource metadata URLs. Only enable when the server is deployed behind a trusted proxy that sets these headers. Ignored when --base-url is set.") @@ -260,6 +262,7 @@ func init() { _ = viper.BindPFlag("listen-host", httpCmd.Flags().Lookup("listen-host")) _ = viper.BindPFlag("base-url", httpCmd.Flags().Lookup("base-url")) _ = viper.BindPFlag("base-path", httpCmd.Flags().Lookup("base-path")) + _ = viper.BindPFlag("authorization-server", httpCmd.Flags().Lookup("authorization-server")) _ = viper.BindPFlag("scope-challenge", httpCmd.Flags().Lookup("scope-challenge")) _ = viper.BindPFlag("trust-proxy-headers", httpCmd.Flags().Lookup("trust-proxy-headers")) // Add subcommands diff --git a/docs/streamable-http.md b/docs/streamable-http.md index 8f4a2bff84..1d3c6aede8 100644 --- a/docs/streamable-http.md +++ b/docs/streamable-http.md @@ -71,6 +71,31 @@ github-mcp-server http --trust-proxy-headers Equivalent environment variable: `GITHUB_TRUST_PROXY_HEADERS=1`. Only enable this when the upstream proxy is trusted to set or strip these headers; otherwise prefer `--base-url`. When `--base-url` is set, it always takes precedence and `--trust-proxy-headers` has no effect. +### With an OAuth Proxy (GHES / non-standard authorization server) + +When deploying against GitHub Enterprise Server, GHES does not natively support the OAuth extensions required by MCP (RFC 8414 metadata discovery, RFC 7591 Dynamic Client Registration, PKCE). In this case you can run a separate OAuth proxy that implements the MCP OAuth spec and forwards authentication to GHES. Use `--authorization-server` to advertise the proxy's URL in the protected resource metadata instead of the one derived from `--gh-host`: + +```bash +github-mcp-server http \ + --gh-host https://github.example.com \ + --base-url https://mcp.example.com \ + --authorization-server https://mcp.example.com/oauth-proxy +``` + +The `authorization_servers` field in the protected resource metadata will then point at your proxy: + +```json +{ + "resource": "https://mcp.example.com", + "authorization_servers": [ + "https://mcp.example.com/oauth-proxy" + ], + ... +} +``` + +Equivalent environment variable: `GITHUB_AUTHORIZATION_SERVER=https://mcp.example.com/oauth-proxy`. + ## Client Configuration ### Using OAuth Authentication diff --git a/pkg/http/server.go b/pkg/http/server.go index 36d3e111bc..b70e1d064f 100644 --- a/pkg/http/server.go +++ b/pkg/http/server.go @@ -49,6 +49,13 @@ type ServerConfig struct { // This is used to restore the original path when a proxy strips a base path before forwarding. ResourcePath string + // AuthorizationServer overrides the authorization server URL advertised in the + // OAuth Protected Resource Metadata (/.well-known/oauth-protected-resource). + // When set, this URL is used instead of the one derived from the GitHub host. + // Useful when deploying behind an OAuth proxy (e.g. for GHES, which does not + // natively support RFC 8414 / RFC 7591 / PKCE). + AuthorizationServer string + // TrustProxyHeaders indicates whether X-Forwarded-Host and X-Forwarded-Proto // should be honored when constructing OAuth resource metadata URLs. Only // enable this when the server is deployed behind a trusted proxy that sets @@ -163,9 +170,10 @@ func RunHTTPServer(cfg ServerConfig) error { // Register OAuth protected resource metadata endpoints oauthCfg := &oauth.Config{ - BaseURL: cfg.BaseURL, - ResourcePath: cfg.ResourcePath, - TrustProxyHeaders: cfg.TrustProxyHeaders, + BaseURL: cfg.BaseURL, + ResourcePath: cfg.ResourcePath, + TrustProxyHeaders: cfg.TrustProxyHeaders, + AuthorizationServer: cfg.AuthorizationServer, } serverOptions := []HandlerOption{}