diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..82733cc --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +[*] +indent_style = space +indent_size = 2 + +[*.go] +indent_style = tab +indent_size = 4 diff --git a/Dockerfile b/Dockerfile index 02800da..4177a4d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,13 +10,14 @@ RUN go mod download COPY main.go main.go COPY cmd/ cmd/ COPY config/ config/ +COPY htmlresponse/ htmlresponse/ COPY jsonrpc/ jsonrpc/ COPY log/ log/ COPY oauth/ oauth/ COPY proxy/ proxy/ COPY webhook/ webhook/ RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} \ - go build -a -o mcp-gateway -ldflags="-s -w" . + go build -a -o mcp-gateway -ldflags="-s -w" . FROM gcr.io/distroless/static-debian12:nonroot WORKDIR / diff --git a/htmlresponse/handle.go b/htmlresponse/handle.go new file mode 100644 index 0000000..b670e95 --- /dev/null +++ b/htmlresponse/handle.go @@ -0,0 +1,53 @@ +package htmlresponse + +import ( + _ "embed" + "html/template" + "net/http" + "net/url" + "slices" + "strings" + + "github.com/hyprmcp/mcp-gateway/config" +) + +var ( + //go:embed template.html + ts string + tpl *template.Template +) + +func init() { + if t, err := template.New("").Parse(ts); err != nil { + panic(err) + } else { + tpl = t + } +} + +type handler struct { + config *config.Config +} + +func NewHandler(config *config.Config) *handler { + return &handler{config: config} +} + +func (h *handler) Handle(w http.ResponseWriter, r *http.Request) error { + var data struct { + Name string + Url string + } + + u, _ := url.Parse(h.config.Host.String()) + u.Path = r.URL.Path + u.RawQuery = r.URL.RawQuery + data.Url = u.String() + + ps := strings.Split(r.URL.Path, "/") + if nameIdx := slices.IndexFunc(ps, func(s string) bool { return s != "" && s != "mcp" }); nameIdx >= 0 { + data.Name = ps[nameIdx] + } + + return tpl.Execute(w, data) +} diff --git a/htmlresponse/template.html b/htmlresponse/template.html new file mode 100644 index 0000000..b7708df --- /dev/null +++ b/htmlresponse/template.html @@ -0,0 +1,94 @@ + + + + Install Instructions - {{ .Name }} + + + + + +
+
+
Install {{ or .Name .Url }}
+
+ It looks like you're trying to install the MCP Server + {{ or .Name .Url }} but you've tried opening it in + your browser. If you're not sure how to proceed, don't worry, we've + got you covered! Just choose your preferred AI tool below and follow + the instructions: +
+ +
+ + +
+ + diff --git a/oauth/oauth.go b/oauth/oauth.go index 9432a03..7d183d1 100644 --- a/oauth/oauth.go +++ b/oauth/oauth.go @@ -11,6 +11,7 @@ import ( "github.com/go-chi/httprate" "github.com/hyprmcp/mcp-gateway/config" + "github.com/hyprmcp/mcp-gateway/htmlresponse" "github.com/hyprmcp/mcp-gateway/log" "github.com/lestrrat-go/httprc/v3" "github.com/lestrrat-go/httprc/v3/errsink" @@ -84,10 +85,13 @@ func (mgr *Manager) Register(mux *http.ServeMux) error { } func (mgr *Manager) Handler(next http.Handler) http.Handler { + htmlHandler := htmlresponse.NewHandler(mgr.config) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { rawToken := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(r.Header.Get("Authorization")), "Bearer")) + shouldCallNext := true + token, err := jwt.ParseString(rawToken, jwt.WithKeySet(mgr.jwkSet)) if err != nil { metadataURL, _ := url.Parse(mgr.config.Host.String()) @@ -98,9 +102,18 @@ func (mgr *Manager) Handler(next http.Handler) http.Handler { fmt.Sprintf(`Bearer resource_metadata="%s"`, metadataURL.String()), ) w.WriteHeader(http.StatusUnauthorized) - return + shouldCallNext = false + } + + if strings.Contains(r.Header.Get("Accept"), "text/html") { + if err := htmlHandler.Handle(w, r); err != nil { + log.Get(r.Context()).Error(err, "failed to handle html response") + } + shouldCallNext = false } - next.ServeHTTP(w, r.WithContext(TokenContext(r.Context(), token, rawToken))) + if shouldCallNext { + next.ServeHTTP(w, r.WithContext(TokenContext(r.Context(), token, rawToken))) + } }) }