MCPcopy Create free account
hub / github.com/modelcontextprotocol/go-sdk / ServeHTTP

Method ServeHTTP

mcp/streamable.go:255–546  ·  view source on GitHub ↗
(w http.ResponseWriter, req *http.Request)

Source from the content-addressed store, hash-verified

253var disablecontenttypecheck = mcpgodebug.Value("disablecontenttypecheck")
254
255func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
256 // DNS rebinding protection: auto-enabled for localhost servers.
257 // See: https://modelcontextprotocol.io/specification/2025-11-25/basic/security_best_practices#local-mcp-server-compromise
258 if !h.opts.DisableLocalhostProtection && disablelocalhostprotection != "1" {
259 if localAddr, ok := req.Context().Value(http.LocalAddrContextKey).(net.Addr); ok && localAddr != nil {
260 if util.IsLoopback(localAddr.String()) && !util.IsLoopback(req.Host) {
261 http.Error(w, fmt.Sprintf("Forbidden: invalid Host header %q", req.Host), http.StatusForbidden)
262 return
263 }
264 }
265 }
266
267 if h.opts.CrossOriginProtection != nil {
268 // Verify the 'Origin' header to protect against CSRF attacks.
269 if err := h.opts.CrossOriginProtection.Check(req); err != nil {
270 http.Error(w, err.Error(), http.StatusForbidden)
271 return
272 }
273 }
274
275 // Validate 'Content-Type' header.
276 if disablecontenttypecheck != "1" && req.Method == http.MethodPost && baseMediaType(req.Header.Get("Content-Type")) != "application/json" {
277 http.Error(w, "Content-Type must be 'application/json'", http.StatusUnsupportedMediaType)
278 return
279 }
280
281 // Allow multiple 'Accept' headers.
282 // https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept#syntax
283 jsonOK, streamOK := streamableAccepts(req.Header.Values("Accept"))
284
285 if req.Method == http.MethodGet {
286 if !streamOK {
287 http.Error(w, "Accept must contain 'text/event-stream' for GET requests", http.StatusBadRequest)
288 return
289 }
290 } else if (!jsonOK || !streamOK) && req.Method != http.MethodDelete { // TODO: consolidate with handling of http method below.
291 http.Error(w, "Accept must contain both 'application/json' and 'text/event-stream'", http.StatusBadRequest)
292 return
293 }
294
295 sessionID := req.Header.Get(sessionIDHeader)
296 var sessInfo *sessionInfo
297 if sessionID != "" {
298 h.mu.Lock()
299 sessInfo = h.sessions[sessionID]
300 h.mu.Unlock()
301 if sessInfo == nil && !h.opts.Stateless {
302 // Unless we're in 'stateless' mode, which doesn't perform any Session-ID
303 // validation, we require that the session ID matches a known session.
304 //
305 // In stateless mode, a temporary transport is be created below.
306 http.Error(w, "session not found", http.StatusNotFound)
307 return
308 }
309 // Prevent session hijacking: if the session was created with a user ID,
310 // verify that subsequent requests come from the same user.
311 if sessInfo != nil && sessInfo.userID != "" {
312 tokenInfo := auth.TokenInfoFromContext(req.Context())

Calls 13

startPOSTMethod · 0.95
endPOSTMethod · 0.95
IsLoopbackFunction · 0.92
TokenInfoFromContextFunction · 0.92
baseMediaTypeFunction · 0.85
streamableAcceptsFunction · 0.85
readBatchFunction · 0.85
stopTimerMethod · 0.80
InitializeParamsMethod · 0.80
CloseMethod · 0.65
ConnectMethod · 0.65
ValueMethod · 0.45

Tested by 2

TestStreamableTransportsFunction · 0.76