|
8 | 8 | "fmt" |
9 | 9 | "html" |
10 | 10 | "net/http" |
| 11 | + "net/url" |
11 | 12 | "strings" |
12 | 13 | "text/template" |
13 | 14 |
|
@@ -112,14 +113,16 @@ func (h *AuthHandler) RegisterRoutes(r chi.Router) { |
112 | 113 | } |
113 | 114 |
|
114 | 115 | // routesForPattern generates route variants for a given pattern. |
| 116 | +// GitHub strips the /mcp prefix before forwarding, so we register both variants: |
| 117 | +// - With /mcp prefix: for direct access or when GitHub doesn't strip |
| 118 | +// - Without /mcp prefix: for when GitHub has stripped the prefix |
115 | 119 | func (h *AuthHandler) routesForPattern(pattern string) []string { |
116 | | - routes := []string{ |
| 120 | + return []string{ |
117 | 121 | pattern, |
| 122 | + "/mcp" + pattern, |
118 | 123 | pattern + "/", |
119 | | - pattern + "/mcp", |
120 | | - pattern + "/mcp/", |
| 124 | + "/mcp" + pattern + "/", |
121 | 125 | } |
122 | | - return routes |
123 | 126 | } |
124 | 127 |
|
125 | 128 | // handleProtectedResource handles requests for OAuth protected resource metadata. |
@@ -153,26 +156,43 @@ func (h *AuthHandler) handleProtectedResource(w http.ResponseWriter, r *http.Req |
153 | 156 | _, _ = w.Write(buf.Bytes()) |
154 | 157 | } |
155 | 158 |
|
| 159 | +// GetEffectiveResourcePath returns the resource path for OAuth protected resource URLs. |
| 160 | +// It checks for the X-GitHub-Original-Path header set by copilot-api (CAPI), which contains |
| 161 | +// the exact path the client requested before the /mcp prefix was stripped. |
| 162 | +// If the header is not present (e.g., direct access or older CAPI versions), it falls back to |
| 163 | +// restoring the /mcp prefix. |
| 164 | +func GetEffectiveResourcePath(r *http.Request) string { |
| 165 | + // Check for the original path header from copilot-api (preferred method) |
| 166 | + if originalPath := r.Header.Get(headers.OriginalPathHeader); originalPath != "" { |
| 167 | + return originalPath |
| 168 | + } |
| 169 | + |
| 170 | + // Fallback: copilot-api strips /mcp prefix, so we need to restore it for the external URL |
| 171 | + if r.URL.Path == "/" { |
| 172 | + return "/mcp" |
| 173 | + } |
| 174 | + return "/mcp" + r.URL.Path |
| 175 | +} |
| 176 | + |
156 | 177 | // GetProtectedResourceData builds the OAuth protected resource data for a request. |
157 | 178 | func (h *AuthHandler) GetProtectedResourceData(r *http.Request, resourcePath string) (*ProtectedResourceData, error) { |
158 | 179 | host, scheme := GetEffectiveHostAndScheme(r, h.cfg) |
159 | 180 |
|
160 | | - // Build the resource URL |
161 | | - var resourceURL string |
| 181 | + // Build the base URL |
| 182 | + baseURL := fmt.Sprintf("%s://%s", scheme, host) |
162 | 183 | if h.cfg.BaseURL != "" { |
163 | | - // Use configured base URL |
164 | | - baseURL := strings.TrimSuffix(h.cfg.BaseURL, "/") |
165 | | - if resourcePath == "/" { |
166 | | - resourceURL = baseURL + "/" |
167 | | - } else { |
168 | | - resourceURL = baseURL + "/" + resourcePath |
169 | | - } |
| 184 | + baseURL = strings.TrimSuffix(h.cfg.BaseURL, "/") |
| 185 | + } |
| 186 | + |
| 187 | + // Build the resource URL using url.JoinPath for proper path handling |
| 188 | + var resourceURL string |
| 189 | + var err error |
| 190 | + if resourcePath == "/" { |
| 191 | + resourceURL = baseURL + "/" |
170 | 192 | } else { |
171 | | - // Derive from request |
172 | | - if resourcePath == "/" { |
173 | | - resourceURL = fmt.Sprintf("%s://%s/", scheme, host) |
174 | | - } else { |
175 | | - resourceURL = fmt.Sprintf("%s://%s/%s", scheme, host, resourcePath) |
| 193 | + resourceURL, err = url.JoinPath(baseURL, resourcePath) |
| 194 | + if err != nil { |
| 195 | + return nil, fmt.Errorf("failed to build resource URL: %w", err) |
176 | 196 | } |
177 | 197 | } |
178 | 198 |
|
|
0 commit comments