Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[*]
indent_style = space
indent_size = 2

[*.go]
indent_style = tab
indent_size = 4
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 /
Expand Down
53 changes: 53 additions & 0 deletions htmlresponse/handle.go
Original file line number Diff line number Diff line change
@@ -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)
}
94 changes: 94 additions & 0 deletions htmlresponse/template.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<!doctype html>
<html>
<head>
<title>Install Instructions - {{ .Name }}</title>
<script src="https://cdn.jsdelivr.net/npm/@hyprmcp/mcp-install-instructions-generator@0.1.0/dist/component/index.js"></script>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@hyprmcp/mcp-install-instructions-generator@0.1.0/dist/component/index.css"
/>
<style>
body {
background: #f0f0f0;
color: #282828;
margin: 0;
font-family: sans-serif;
}

main {
max-width: 52rem;
margin: 0 auto;
}

.card {
box-shadow:
0 2px 4px rgba(0, 0, 0, 0.1),
0 8px 16px rgba(0, 0, 0, 0.1);
background: #fcfcfc;
margin: 2.4rem 0 0.8rem;
padding: 2.4rem;
border-radius: 0.8rem;
}

.hero {
text-align: center;
font-size: 2.4rem;
font-weight: bold;
}

.intro {
margin: 2.4rem 0;
}

h2,
h3,
h4,
h5,
h6 {
margin-bottom: 0.4rem;
}

button {
background: #000000;
color: #fff;
border: none;
border-radius: 0.4rem;
padding: 0.5rem 1rem;
cursor: pointer;
font-weight: 500;
}

.footer {
display: flex;
gap: 0.2rem;
justify-content: flex-end;
align-items: center;
}
</style>
</head>
<body>
<main>
<div class="card">
<div class="hero">Install {{ or .Name .Url }}</div>
<div class="intro">
It looks like you're trying to install the MCP Server
<strong>{{ or .Name .Url }}</strong> 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:
</div>
<mcp-install-instructions
url="{{ .Url }}"
name="{{ .Name }}"
></mcp-install-instructions>
</div>

<div class="footer">
Powered by
<a href="https://hyprmcp.com/" target="_blank"
><img src="https://hyprmcp.com/hyprmcp_20px.svg"
/></a>
</div>
</main>
</body>
</html>
17 changes: 15 additions & 2 deletions oauth/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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())
Expand All @@ -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)))
}
})
}