@@ -129,37 +129,101 @@ func TestHandlerRegisterRoutes(t *testing.T) {
129129 r := chi .NewRouter ()
130130 NewHandler (Config {}).RegisterRoutes (r )
131131
132- for _ , path := range []string {Path , "/mcp" + Path } {
133- t .Run (path , func (t * testing.T ) {
134- t .Parallel ()
132+ t .Run ("GET serves the card at the canonical path" , func (t * testing.T ) {
133+ t .Parallel ()
135134
136- req := httptest .NewRequest (http .MethodGet , path , nil )
137- rec := httptest .NewRecorder ()
138- r .ServeHTTP (rec , req )
135+ req := httptest .NewRequest (http .MethodGet , Path , nil )
136+ rec := httptest .NewRecorder ()
137+ r .ServeHTTP (rec , req )
139138
140- res := rec .Result ()
141- defer res .Body .Close ()
139+ res := rec .Result ()
140+ defer res .Body .Close ()
142141
143- assert .Equal (t , http .StatusOK , res .StatusCode )
144- assert .Equal (t , MediaType , res .Header .Get (headers .ContentTypeHeader ))
145- })
142+ assert .Equal (t , http .StatusOK , res .StatusCode )
143+ assert .Equal (t , MediaType , res .Header .Get (headers .ContentTypeHeader ))
144+ })
146145
147- t .Run (path + " POST owned by card handler" , func (t * testing.T ) {
148- t .Parallel ()
146+ t .Run (" POST owned by card handler" , func (t * testing.T ) {
147+ t .Parallel ()
149148
150- // The handler is registered for all methods so non-GET requests are
151- // answered here (405) rather than falling through to another route.
152- req := httptest .NewRequest (http .MethodPost , path , nil )
153- rec := httptest .NewRecorder ()
154- r .ServeHTTP (rec , req )
149+ // The handler is registered for all methods so non-GET requests are
150+ // answered here (405) rather than falling through to another route.
151+ req := httptest .NewRequest (http .MethodPost , Path , nil )
152+ rec := httptest .NewRecorder ()
153+ r .ServeHTTP (rec , req )
155154
156- res := rec .Result ()
157- defer res .Body .Close ()
155+ res := rec .Result ()
156+ defer res .Body .Close ()
158157
159- assert .Equal (t , http .StatusMethodNotAllowed , res .StatusCode )
160- assert .Equal (t , "*" , res .Header .Get ("Access-Control-Allow-Origin" ))
161- })
162- }
158+ assert .Equal (t , http .StatusMethodNotAllowed , res .StatusCode )
159+ assert .Equal (t , "*" , res .Header .Get ("Access-Control-Allow-Origin" ))
160+ })
161+
162+ t .Run ("card is served at exactly one path" , func (t * testing.T ) {
163+ t .Parallel ()
164+
165+ // The card must be discoverable at a single canonical location only;
166+ // no alternate path (e.g. /mcp/server-card) is registered.
167+ req := httptest .NewRequest (http .MethodGet , "/mcp" + Path , nil )
168+ rec := httptest .NewRecorder ()
169+ r .ServeHTTP (rec , req )
170+
171+ res := rec .Result ()
172+ defer res .Body .Close ()
173+
174+ assert .Equal (t , http .StatusNotFound , res .StatusCode )
175+ })
176+ }
177+
178+ // TestHandlerRegisterRoutesNotShadowedByCatchAll mirrors the production wiring
179+ // (pkg/http/server.go), where the streamable MCP endpoint is mounted as a
180+ // catch-all at "/" (pkg/http/handler.go: r.Mount("/", h)). The card's static
181+ // route must take precedence over that wildcard mount so the card — and not the
182+ // auth-gated MCP endpoint — answers GET and non-GET requests at the card path.
183+ func TestHandlerRegisterRoutesNotShadowedByCatchAll (t * testing.T ) {
184+ t .Parallel ()
185+
186+ const mcpStatus = http .StatusUnauthorized // sentinel for the auth-gated MCP endpoint
187+
188+ r := chi .NewRouter ()
189+ r .Group (func (r chi.Router ) {
190+ r .Mount ("/" , http .HandlerFunc (func (w http.ResponseWriter , _ * http.Request ) {
191+ w .WriteHeader (mcpStatus )
192+ }))
193+ })
194+ r .Group (func (r chi.Router ) {
195+ NewHandler (Config {}).RegisterRoutes (r )
196+ })
197+
198+ t .Run ("GET is owned by the card handler" , func (t * testing.T ) {
199+ t .Parallel ()
200+
201+ req := httptest .NewRequest (http .MethodGet , Path , nil )
202+ rec := httptest .NewRecorder ()
203+ r .ServeHTTP (rec , req )
204+
205+ res := rec .Result ()
206+ defer res .Body .Close ()
207+
208+ assert .Equal (t , http .StatusOK , res .StatusCode )
209+ assert .Equal (t , MediaType , res .Header .Get (headers .ContentTypeHeader ))
210+ })
211+
212+ t .Run ("non-GET is owned by the card handler, not the catch-all" , func (t * testing.T ) {
213+ t .Parallel ()
214+
215+ // A POST must get the card handler's 405, not the MCP catch-all's
216+ // sentinel status — proving the static route is not shadowed.
217+ req := httptest .NewRequest (http .MethodPost , Path , nil )
218+ rec := httptest .NewRecorder ()
219+ r .ServeHTTP (rec , req )
220+
221+ res := rec .Result ()
222+ defer res .Body .Close ()
223+
224+ assert .Equal (t , http .StatusMethodNotAllowed , res .StatusCode )
225+ assert .NotEqual (t , mcpStatus , res .StatusCode )
226+ })
163227}
164228
165229func TestHandlerETagConditionalRequests (t * testing.T ) {
0 commit comments