refactor: remove legacy MCPGODEBUG compatibility for new protocol release (#1237)

This commit is contained in:
Guglielmo Colombo 2026-09-04 10:08:52 +02:00 • committed by GitHub
commit 3f3b699b2b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 41 additions and 42 deletions

View file

@ -44,6 +44,23 @@ Options listed below were added and will be removed in the 1.9.0 version of the
soon as its context is cancelled and cannot be delayed by a slow or soon as its context is cancelled and cannot be delayed by a slow or
unresponsive peer. See issue #1150. unresponsive peer. See issue #1150.
Options below were removed, according to plan:
- `seterroroverwrite`. `SetError` now always preserves existing `Content`.
- `enableoriginverification`. A nil
`StreamableHTTPOptions.CrossOriginProtection` now always means no
cross-origin protection. Wrap the handler with cross-origin protection
middleware instead.
- `disablecontenttypecheck`. Content-Type validation on HTTP POST requests can
no longer be disabled.
- `disablelocalhostprotection`, whose removal had been postponed to this
release. Set the `DisableLocalhostProtection` field in
`StreamableHTTPOptions` or `SSEOptions` to opt out of DNS rebinding
protection.
### 1.7.0 ### 1.7.0
Options listed below were added and will be removed in the 1.9.0 version of the SDK. Options listed below were added and will be removed in the 1.9.0 version of the SDK.

View file

@ -43,6 +43,23 @@ Options listed below were added and will be removed in the 1.9.0 version of the
soon as its context is cancelled and cannot be delayed by a slow or soon as its context is cancelled and cannot be delayed by a slow or
unresponsive peer. See issue #1150. unresponsive peer. See issue #1150.
Options below were removed, according to plan:
- `seterroroverwrite`. `SetError` now always preserves existing `Content`.
- `enableoriginverification`. A nil
`StreamableHTTPOptions.CrossOriginProtection` now always means no
cross-origin protection. Wrap the handler with cross-origin protection
middleware instead.
- `disablecontenttypecheck`. Content-Type validation on HTTP POST requests can
no longer be disabled.
- `disablelocalhostprotection`, whose removal had been postponed to this
release. Set the `DisableLocalhostProtection` field in
`StreamableHTTPOptions` or `SSEOptions` to opt out of DNS rebinding
protection.
### 1.7.0 ### 1.7.0
Options listed below were added and will be removed in the 1.9.0 version of the SDK. Options listed below were added and will be removed in the 1.9.0 version of the SDK.

View file

@ -329,23 +329,13 @@ type CallToolResult struct {
err error err error
} }
// seterroroverwrite is a compatibility parameter that restores the pre-1.6.0
// behavior of [CallToolResult.SetError], where Content was always overwritten
// with the error text. See the documentation for the mcpgodebug package for
// instructions on how to enable it.
// The option will be removed in the 1.8.0 version of the SDK.
var seterroroverwrite = mcpgodebug.Value("seterroroverwrite")
// SetError sets the error for the tool result and sets IsError to true. // SetError sets the error for the tool result and sets IsError to true.
// If Content has not already been populated, it is set to the error text. // If Content has not already been populated, it is set to the error text.
// If Content has already been populated, it is left unchanged, allowing callers // If Content has already been populated, it is left unchanged, allowing callers
// to provide a user-friendly message while still recording the underlying error // to provide a user-friendly message while still recording the underlying error
// for inspection via [GetError] in server middleware. // for inspection via [GetError] in server middleware.
//
// To restore the previous behavior where Content was always overwritten,
// set MCPGODEBUG=seterroroverwrite=1.
func (r *CallToolResult) SetError(err error) { func (r *CallToolResult) SetError(err error) {
if len(r.Content) == 0 || seterroroverwrite == "1" { if len(r.Content) == 0 {
r.Content = []Content{&TextContent{Text: err.Error()}} r.Content = []Content{&TextContent{Text: err.Error()}}
} }
r.IsError = true r.IsError = true

View file

@ -225,7 +225,7 @@ func (t *SSEServerTransport) SupportsProtocolVersion(version string) bool {
func (h *SSEHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (h *SSEHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// DNS rebinding protection: auto-enabled for localhost servers. // DNS rebinding protection: auto-enabled for localhost servers.
// See: https://modelcontextprotocol.io/specification/2025-11-25/basic/security_best_practices#local-mcp-server-compromise // See: https://modelcontextprotocol.io/specification/2025-11-25/basic/security_best_practices#local-mcp-server-compromise
if !h.opts.DisableLocalhostProtection && disablelocalhostprotection != "1" { if !h.opts.DisableLocalhostProtection {
if localAddr, ok := req.Context().Value(http.LocalAddrContextKey).(net.Addr); ok && localAddr != nil { if localAddr, ok := req.Context().Value(http.LocalAddrContextKey).(net.Addr); ok && localAddr != nil {
if util.IsLoopback(localAddr.String()) && !util.IsLoopback(req.Host) { if util.IsLoopback(localAddr.String()) && !util.IsLoopback(req.Host) {
http.Error(w, fmt.Sprintf("Forbidden: invalid Host header %q", req.Host), http.StatusForbidden) http.Error(w, fmt.Sprintf("Forbidden: invalid Host header %q", req.Host), http.StatusForbidden)
@ -235,7 +235,7 @@ func (h *SSEHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
// Validate 'Content-Type' header. // Validate 'Content-Type' header.
if disablecontenttypecheck != "1" && req.Method == http.MethodPost { if req.Method == http.MethodPost {
mediaType, _, err := mime.ParseMediaType(req.Header.Get("Content-Type")) mediaType, _, err := mime.ParseMediaType(req.Header.Get("Content-Type"))
if err != nil || mediaType != "application/json" { if err != nil || mediaType != "application/json" {
http.Error(w, "Content-Type must be 'application/json'", http.StatusUnsupportedMediaType) http.Error(w, "Content-Type must be 'application/json'", http.StatusUnsupportedMediaType)

View file

@ -184,8 +184,7 @@ type StreamableHTTPOptions struct {
// CrossOriginProtection allows to customize cross-origin protection. // CrossOriginProtection allows to customize cross-origin protection.
// The deny handler set in the CrossOriginProtection through SetDenyHandler // The deny handler set in the CrossOriginProtection through SetDenyHandler
// is ignored. // is ignored.
// If nil, no cross-origin protection is applied. Use the `enableoriginverification` // If nil, no cross-origin protection is applied.
// MCPGODEBUG compatibility parameter to enable the default protection until v1.8.0.
// //
// Deprecated: wrap the handler with cross-origin protection middleware // Deprecated: wrap the handler with cross-origin protection middleware
// instead. For example: // instead. For example:
@ -240,10 +239,6 @@ func NewStreamableHTTPHandler(getServer func(*http.Request) *Server, opts *Strea
h.opts.Logger = ensureLogger(h.opts.Logger) h.opts.Logger = ensureLogger(h.opts.Logger)
if h.opts.CrossOriginProtection == nil && enableoriginverification == "1" {
h.opts.CrossOriginProtection = &http.CrossOriginProtection{}
}
if h.opts.MaxRequestBodyBytes == 0 { if h.opts.MaxRequestBodyBytes == 0 {
h.opts.MaxRequestBodyBytes = DefaultMaxRequestBodyBytes h.opts.MaxRequestBodyBytes = DefaultMaxRequestBodyBytes
} }
@ -274,20 +269,6 @@ func (h *StreamableHTTPHandler) closeAll() {
} }
} }
// disablelocalhostprotection is a compatibility parameter that allows to disable
// DNS rebinding protection, which was added in the 1.4.0 version of the SDK.
// See the documentation for the mcpgodebug package for instructions how to enable it.
// The option will be removed in the 1.8.0 version of the SDK.
var disablelocalhostprotection = mcpgodebug.Value("disablelocalhostprotection")
// enableoriginverification is a compatibility parameter that restores the
// default cross-origin protection behavior from v1.4.1-v1.5.0. When set to
// "1", a zero-value CrossOriginProtection will be applied if none is
// explicitly provided in StreamableHTTPOptions.
// See the documentation for the mcpgodebug package for instructions how to enable it.
// The option will be removed in the 1.8.0 version of the SDK.
var enableoriginverification = mcpgodebug.Value("enableoriginverification")
// allowsessionsinstateless is a compatibility parameter that restores the old // allowsessionsinstateless is a compatibility parameter that restores the old
// behavior of reading and using Mcp-Session-Id headers in stateless mode. When // behavior of reading and using Mcp-Session-Id headers in stateless mode. When
// set to "1", stateless servers will read the session ID from the request // set to "1", stateless servers will read the session ID from the request
@ -308,12 +289,6 @@ var allowsessionsinstateless = mcpgodebug.Value("allowsessionsinstateless")
// permanently fails the connection. // permanently fails the connection.
var noprotocolerrorbody = mcpgodebug.Value("noprotocolerrorbody") var noprotocolerrorbody = mcpgodebug.Value("noprotocolerrorbody")
// disablecontenttypecheck is a compatibility parameter that allows to disable
// Content-Type validation on POST requests.
// See the documentation for the mcpgodebug package for instructions how to enable it.
// The option will be removed in the 1.8.0 version of the SDK.
var disablecontenttypecheck = mcpgodebug.Value("disablecontenttypecheck")
// plaintextstatefulrejection is a compatibility parameter that restores the // plaintextstatefulrejection is a compatibility parameter that restores the
// previous behavior of a stateful [StreamableHTTPHandler] when it receives a // previous behavior of a stateful [StreamableHTTPHandler] when it receives a
// request carrying SEP-2575 per-request metadata (i.e. an // request carrying SEP-2575 per-request metadata (i.e. an
@ -341,7 +316,7 @@ func writeJSONRPCError(w http.ResponseWriter, status int, id jsonrpc.ID, jerr *j
func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// DNS rebinding protection: auto-enabled for localhost servers. // DNS rebinding protection: auto-enabled for localhost servers.
// See: https://modelcontextprotocol.io/specification/2025-11-25/basic/security_best_practices#local-mcp-server-compromise // See: https://modelcontextprotocol.io/specification/2025-11-25/basic/security_best_practices#local-mcp-server-compromise
if !h.opts.DisableLocalhostProtection && disablelocalhostprotection != "1" { if !h.opts.DisableLocalhostProtection {
if localAddr, ok := req.Context().Value(http.LocalAddrContextKey).(net.Addr); ok && localAddr != nil { if localAddr, ok := req.Context().Value(http.LocalAddrContextKey).(net.Addr); ok && localAddr != nil {
if util.IsLoopback(localAddr.String()) && !util.IsLoopback(req.Host) { if util.IsLoopback(localAddr.String()) && !util.IsLoopback(req.Host) {
http.Error(w, fmt.Sprintf("Forbidden: invalid Host header %q", req.Host), http.StatusForbidden) http.Error(w, fmt.Sprintf("Forbidden: invalid Host header %q", req.Host), http.StatusForbidden)
@ -410,7 +385,7 @@ func (h *StreamableHTTPHandler) serveStateless(w http.ResponseWriter, req *http.
return return
} }
if disablecontenttypecheck != "1" && baseMediaType(req.Header.Get("Content-Type")) != "application/json" { if baseMediaType(req.Header.Get("Content-Type")) != "application/json" {
http.Error(w, "Content-Type must be 'application/json'", http.StatusUnsupportedMediaType) http.Error(w, "Content-Type must be 'application/json'", http.StatusUnsupportedMediaType)
return return
} }
@ -644,7 +619,7 @@ func (h *StreamableHTTPHandler) serveStatefulDELETE(w http.ResponseWriter, req *
// ID, a new session is created (this is the normal path for the first // ID, a new session is created (this is the normal path for the first
// initialize request). // initialize request).
func (h *StreamableHTTPHandler) serveStatefulPOST(w http.ResponseWriter, req *http.Request) { func (h *StreamableHTTPHandler) serveStatefulPOST(w http.ResponseWriter, req *http.Request) {
if disablecontenttypecheck != "1" && baseMediaType(req.Header.Get("Content-Type")) != "application/json" { if baseMediaType(req.Header.Get("Content-Type")) != "application/json" {
http.Error(w, "Content-Type must be 'application/json'", http.StatusUnsupportedMediaType) http.Error(w, "Content-Type must be 'application/json'", http.StatusUnsupportedMediaType)
return return
} }