MCPcopy Create free account
hub / github.com/PerpetualSoftware/pad / buildHTTPRequest

Function buildHTTPRequest

internal/mcp/dispatch_http.go:583–649  ·  view source on GitHub ↗

buildHTTPRequest constructs the in-process request, attaching the user via the exported server.WithCurrentUser helper so the handler chain treats the call as authenticated. Pulled out so tests can inspect / decorate it cheaply.

(ctx context.Context, method, urlPath string, body []byte, user *models.User)

Source from the content-addressed store, hash-verified

581// chain treats the call as authenticated. Pulled out so tests can
582// inspect / decorate it cheaply.
583func buildHTTPRequest(ctx context.Context, method, urlPath string, body []byte, user *models.User) (*http.Request, error) {
584 // Strip any inherited chi.RouteCtxKey from the inbound context
585 // before synthesizing the new request. Without this, every
586 // production /mcp tool call 404s on the synthesized /api/v1/...
587 // request because chi's Mux.ServeHTTP short-circuits when it
588 // detects an existing RouteCtxKey:
589 //
590 // // chi/v5/mux.go:71-75
591 // rctx, _ := r.Context().Value(RouteCtxKey).(*Context)
592 // if rctx != nil {
593 // mx.handler.ServeHTTP(w, r) // bypass fresh routing
594 // return
595 // }
596 //
597 // chi assumes "if there's already a route context, I'm being
598 // invoked as a sub-router from a parent — don't reset state."
599 // That's correct for chi's own Sub() / Mount() patterns, but
600 // here we're synthesizing a brand-new request that needs to
601 // route from scratch against the ROOT mux. The stale RouteCtxKey
602 // from the inbound /mcp request causes chi to skip its
603 // rctx.Reset() + RoutePath = "/api/v1/..." setup; the route
604 // table lookup runs against contaminated routing state and
605 // falls through to chi's default NotFound handler — whose body
606 // is the literal "404 page not found\n" the production user
607 // reported on every dispatcher call.
608 //
609 // In tests this never fired because Dispatch was always called
610 // with context.Background() (no RouteCtxKey to inherit). In
611 // production every call enters via /mcp's chi-routed handler,
612 // so the contamination is universal.
613 //
614 // Setting the value to a typed nil shadows the parent's value:
615 // chi's `.(*Context)` type assertion on a context.Value of nil
616 // returns (nil, false), the `rctx != nil` check fails, and
617 // chi takes the fresh-routing branch as intended.
618 //
619 // Critically we DON'T strip pad's own context values
620 // (WithCurrentUser, WithAPITokenAuth, TokenScopes,
621 // TokenAllowedWorkspaces) — those are added below / preserved
622 // from the inbound request and are exactly what the synthesized
623 // request needs to authenticate as the same user. We only strip
624 // the chi-specific routing key.
625 ctx = context.WithValue(ctx, chi.RouteCtxKey, (*chi.Context)(nil))
626
627 var bodyReader io.Reader
628 if len(body) > 0 {
629 bodyReader = bytes.NewReader(body)
630 }
631 req, err := http.NewRequestWithContext(ctx, method, urlPath, bodyReader)
632 if err != nil {
633 return nil, err
634 }
635 if len(body) > 0 {
636 req.Header.Set("Content-Type", "application/json")
637 }
638 // Loopback-equivalent — handlers gating on RemoteAddr (e.g. the
639 // localhost-bootstrap path) treat this as in-process. The auth
640 // chain sees us as already-authenticated via WithCurrentUser, so

Callers 2

buildAuthedRequestMethod · 0.85

Calls 3

WithCurrentUserFunction · 0.92
WithAPITokenAuthFunction · 0.92
SetMethod · 0.45

Tested by 1