@@ -174,9 +174,15 @@ type StreamableHTTPOptions struct {
174174 // CrossOriginProtection allows to customize cross-origin protection.
175175 // The deny handler set in the CrossOriginProtection through SetDenyHandler
176176 // is ignored.
177- // If nil, default (zero-value) cross-origin protection will be used.
178- // Use `disablecrossoriginprotection` MCPGODEBUG compatibility parameter
179- // to disable the default protection until v1.7.0.
177+ // If nil, no cross-origin protection is applied. Use the `enableoriginverification`
178+ // MCPGODEBUG compatibility parameter to enable the default protection until v1.8.0.
179+ //
180+ // Deprecated: wrap the handler with cross-origin protection middleware
181+ // instead. For example:
182+ //
183+ // handler := mcp.NewStreamableHTTPHandler(...)
184+ // protection := http.NewCrossOriginProtection()
185+ // protectedHandler := protection.Handler(handler)
180186 CrossOriginProtection * http.CrossOriginProtection
181187}
182188
@@ -196,7 +202,7 @@ func NewStreamableHTTPHandler(getServer func(*http.Request) *Server, opts *Strea
196202
197203 h .opts .Logger = ensureLogger (h .opts .Logger )
198204
199- if h .opts .CrossOriginProtection == nil {
205+ if h .opts .CrossOriginProtection == nil && enableoriginverification == "1" {
200206 h .opts .CrossOriginProtection = & http.CrossOriginProtection {}
201207 }
202208
@@ -229,15 +235,16 @@ func (h *StreamableHTTPHandler) closeAll() {
229235// disablelocalhostprotection is a compatibility parameter that allows to disable
230236// DNS rebinding protection, which was added in the 1.4.0 version of the SDK.
231237// See the documentation for the mcpgodebug package for instructions how to enable it.
232- // The option will be removed in the 1.7 .0 version of the SDK.
238+ // The option will be removed in the 1.6 .0 version of the SDK.
233239var disablelocalhostprotection = mcpgodebug .Value ("disablelocalhostprotection" )
234240
235- // disablecrossoriginprotection is a compatibility parameter that allows to disable
236- // the verification of the 'Origin' and 'Content-Type' headers, which was added in
237- // the 1.4.1 version of the SDK. See the documentation for the mcpgodebug package
238- // for instructions how to enable it.
239- // The option will be removed in the 1.7.0 version of the SDK.
240- var disablecrossoriginprotection = mcpgodebug .Value ("disablecrossoriginprotection" )
241+ // enableoriginverification is a compatibility parameter that restores the
242+ // default cross-origin protection behavior from v1.4.1-v1.5.0. When set to
243+ // "1", a zero-value CrossOriginProtection will be applied if none is
244+ // explicitly provided in StreamableHTTPOptions.
245+ // See the documentation for the mcpgodebug package for instructions how to enable it.
246+ // The option will be removed in the 1.8.0 version of the SDK.
247+ var enableoriginverification = mcpgodebug .Value ("enableoriginverification" )
241248
242249func (h * StreamableHTTPHandler ) ServeHTTP (w http.ResponseWriter , req * http.Request ) {
243250 // DNS rebinding protection: auto-enabled for localhost servers.
@@ -251,17 +258,18 @@ func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque
251258 }
252259 }
253260
254- if disablecrossoriginprotection != "1" {
261+ if h . opts . CrossOriginProtection != nil {
255262 // Verify the 'Origin' header to protect against CSRF attacks.
256263 if err := h .opts .CrossOriginProtection .Check (req ); err != nil {
257264 http .Error (w , err .Error (), http .StatusForbidden )
258265 return
259266 }
260- // Validate 'Content-Type' header.
261- if req .Method == http .MethodPost && baseMediaType (req .Header .Get ("Content-Type" )) != "application/json" {
262- http .Error (w , "Content-Type must be 'application/json'" , http .StatusUnsupportedMediaType )
263- return
264- }
267+ }
268+
269+ // Validate 'Content-Type' header.
270+ if req .Method == http .MethodPost && baseMediaType (req .Header .Get ("Content-Type" )) != "application/json" {
271+ http .Error (w , "Content-Type must be 'application/json'" , http .StatusUnsupportedMediaType )
272+ return
265273 }
266274
267275 // Allow multiple 'Accept' headers.
@@ -1799,14 +1807,14 @@ func (c *streamableClientConn) Write(ctx context.Context, msg jsonrpc.Message) e
17991807 // Failure to set headers means that the request was not sent.
18001808 // Wrap with ErrRejected so the jsonrpc2 connection doesn't set writeErr
18011809 // and permanently break the connection.
1802- return nil , nil , fmt .Errorf ("%s: %w: %v " , requestSummary , jsonrpc2 .ErrRejected , err )
1810+ return nil , nil , fmt .Errorf ("%s: %w: %w " , requestSummary , jsonrpc2 .ErrRejected , err )
18031811 }
18041812 resp , err := c .client .Do (req )
18051813 if err != nil {
18061814 // Any error from client.Do means the request didn't reach the server.
18071815 // Wrap with ErrRejected so the jsonrpc2 connection doesn't set writeErr
18081816 // and permanently break the connection.
1809- err = fmt .Errorf ("%s: %w: %v " , requestSummary , jsonrpc2 .ErrRejected , err )
1817+ err = fmt .Errorf ("%s: %w: %w " , requestSummary , jsonrpc2 .ErrRejected , err )
18101818 }
18111819 return req , resp , err
18121820 }
@@ -1818,6 +1826,22 @@ func (c *streamableClientConn) Write(ctx context.Context, msg jsonrpc.Message) e
18181826
18191827 if (resp .StatusCode == http .StatusUnauthorized || resp .StatusCode == http .StatusForbidden ) && c .oauthHandler != nil {
18201828 if err := c .oauthHandler .Authorize (ctx , req , resp ); err != nil {
1829+ // If the caller's context was cancelled while we were running the
1830+ // authorization flow, treat the connection as failed so subsequent
1831+ // operations on it (e.g. the cancellation notify the call layer
1832+ // sends in response to ctx cancellation) short-circuit instead of
1833+ // re-invoking the OAuth handler. Otherwise the user gets prompted
1834+ // to authorize a request they have already abandoned. See #882.
1835+ //
1836+ // We check ctx.Err() rather than the error returned by Authorize,
1837+ // because the handler is user-implemented and may return an error
1838+ // that does not wrap context.Canceled (e.g. a custom sentinel or
1839+ // a fmt.Errorf with %v). The context itself is the authoritative
1840+ // source for whether the caller abandoned the request.
1841+ ctxErr := ctx .Err ()
1842+ if errors .Is (ctxErr , context .Canceled ) || errors .Is (ctxErr , context .DeadlineExceeded ) {
1843+ c .fail (fmt .Errorf ("%s: authorization cancelled: %w" , requestSummary , err ))
1844+ }
18211845 // Wrap with ErrRejected so the jsonrpc2 connection doesn't set writeErr
18221846 // and permanently break the connection.
18231847 // Wrap the authorization error as well for client inspection.
0 commit comments