Skip to content

Commit 3a39b66

Browse files
fix(api): allow PATCH in CORS so browsers can call the status endpoints (#1436)
The status endpoints (PATCH /v0/servers/{name}/status and the per-version one) are served with PATCH, but the CORS middleware's AllowedMethods lists GET/POST/PUT/DELETE/OPTIONS and never PATCH. So a cross-origin browser preflight for those endpoints comes back without the allow headers and the browser blocks the actual request. One-line fix: add PATCH to the allowed methods. While here, the existing CORS test turned out to be a placeholder that never reaches the real wrapped handler (its own comments say so), which is why this went unnoticed. Added a small Handler() accessor and a proper preflight test that asserts each routed method including PATCH is allowed.
1 parent c36b521 commit 3a39b66

2 files changed

Lines changed: 69 additions & 0 deletions

File tree

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package api_test
2+
3+
import (
4+
"crypto/ed25519"
5+
"crypto/rand"
6+
"encoding/hex"
7+
"net/http"
8+
"net/http/httptest"
9+
"testing"
10+
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
14+
"github.com/modelcontextprotocol/registry/internal/api"
15+
v0 "github.com/modelcontextprotocol/registry/internal/api/handlers/v0"
16+
"github.com/modelcontextprotocol/registry/internal/config"
17+
"github.com/modelcontextprotocol/registry/internal/telemetry"
18+
)
19+
20+
// TestCORSPreflightAllowedMethods checks that the CORS layer permits every HTTP
21+
// method the API actually routes. The status endpoints
22+
// (PATCH /v0/servers/{name}/status and .../versions/{version}/status) are served
23+
// with PATCH, so a cross-origin browser preflight for PATCH has to be allowed or
24+
// the browser refuses to send the real request. Guards against a routed method
25+
// being missing from AllowedMethods.
26+
func TestCORSPreflightAllowedMethods(t *testing.T) {
27+
seed := make([]byte, ed25519.SeedSize)
28+
_, err := rand.Read(seed)
29+
require.NoError(t, err)
30+
31+
cfg := config.NewConfig()
32+
cfg.JWTPrivateKey = hex.EncodeToString(seed)
33+
34+
shutdownTelemetry, metrics, err := telemetry.InitMetrics("test")
35+
require.NoError(t, err)
36+
defer func() { _ = shutdownTelemetry(nil) }()
37+
38+
// registryService is nil on purpose: a CORS preflight is answered by the
39+
// middleware before any route handler runs, so business logic is never hit.
40+
srv := api.NewServer(cfg, nil, metrics, &v0.VersionBody{Version: "test"})
41+
42+
for _, method := range []string{
43+
http.MethodGet,
44+
http.MethodPost,
45+
http.MethodPut,
46+
http.MethodPatch,
47+
http.MethodDelete,
48+
} {
49+
t.Run(method, func(t *testing.T) {
50+
req := httptest.NewRequest(http.MethodOptions, "/v0/servers/example/status", nil)
51+
req.Header.Set("Origin", "https://example.com")
52+
req.Header.Set("Access-Control-Request-Method", method)
53+
54+
w := httptest.NewRecorder()
55+
srv.Handler().ServeHTTP(w, req)
56+
57+
allow := w.Header().Get("Access-Control-Allow-Methods")
58+
assert.Contains(t, allow, method,
59+
"CORS preflight for %s should be allowed, got Access-Control-Allow-Methods=%q", method, allow)
60+
})
61+
}
62+
}

internal/api/server.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ func NewServer(cfg *config.Config, registryService service.RegistryService, metr
109109
http.MethodGet,
110110
http.MethodPost,
111111
http.MethodPut,
112+
http.MethodPatch,
112113
http.MethodDelete,
113114
http.MethodOptions,
114115
},
@@ -147,6 +148,12 @@ func NewServer(cfg *config.Config, registryService service.RegistryService, metr
147148
return server
148149
}
149150

151+
// Handler returns the fully wrapped HTTP handler (middleware stack plus routes).
152+
// Exposed so tests can exercise the middleware, e.g. CORS, without binding a port.
153+
func (s *Server) Handler() http.Handler {
154+
return s.server.Handler
155+
}
156+
150157
// Start begins listening for incoming HTTP requests
151158
func (s *Server) Start() error {
152159
log.Printf("HTTP server starting on %s", s.config.ServerAddress)

0 commit comments

Comments
 (0)