Skip to content

Commit 876ad98

Browse files
kosmozpmig
andauthored
chore: add responding with html if request has "Accept: text/html" header (#54)
Signed-off-by: Philip <pmig@glasskube.com> Co-authored-by: Philip Miglinci <pmig@glasskube.com>
1 parent 916eeb5 commit 876ad98

File tree

5 files changed

+171
-3
lines changed

5 files changed

+171
-3
lines changed

.editorconfig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[*]
2+
indent_style = space
3+
indent_size = 2
4+
5+
[*.go]
6+
indent_style = tab
7+
indent_size = 4

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@ RUN go mod download
1010
COPY main.go main.go
1111
COPY cmd/ cmd/
1212
COPY config/ config/
13+
COPY htmlresponse/ htmlresponse/
1314
COPY jsonrpc/ jsonrpc/
1415
COPY log/ log/
1516
COPY oauth/ oauth/
1617
COPY proxy/ proxy/
1718
COPY webhook/ webhook/
1819
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} \
19-
go build -a -o mcp-gateway -ldflags="-s -w" .
20+
go build -a -o mcp-gateway -ldflags="-s -w" .
2021

2122
FROM gcr.io/distroless/static-debian12:nonroot
2223
WORKDIR /

htmlresponse/handle.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package htmlresponse
2+
3+
import (
4+
_ "embed"
5+
"html/template"
6+
"net/http"
7+
"net/url"
8+
"slices"
9+
"strings"
10+
11+
"github.com/hyprmcp/mcp-gateway/config"
12+
)
13+
14+
var (
15+
//go:embed template.html
16+
ts string
17+
tpl *template.Template
18+
)
19+
20+
func init() {
21+
if t, err := template.New("").Parse(ts); err != nil {
22+
panic(err)
23+
} else {
24+
tpl = t
25+
}
26+
}
27+
28+
type handler struct {
29+
config *config.Config
30+
}
31+
32+
func NewHandler(config *config.Config) *handler {
33+
return &handler{config: config}
34+
}
35+
36+
func (h *handler) Handle(w http.ResponseWriter, r *http.Request) error {
37+
var data struct {
38+
Name string
39+
Url string
40+
}
41+
42+
u, _ := url.Parse(h.config.Host.String())
43+
u.Path = r.URL.Path
44+
u.RawQuery = r.URL.RawQuery
45+
data.Url = u.String()
46+
47+
ps := strings.Split(r.URL.Path, "/")
48+
if nameIdx := slices.IndexFunc(ps, func(s string) bool { return s != "" && s != "mcp" }); nameIdx >= 0 {
49+
data.Name = ps[nameIdx]
50+
}
51+
52+
return tpl.Execute(w, data)
53+
}

htmlresponse/template.html

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<title>Install Instructions - {{ .Name }}</title>
5+
<script src="https://cdn.jsdelivr.net/npm/@hyprmcp/mcp-install-instructions-generator@0.1.0/dist/component/index.js"></script>
6+
<link
7+
rel="stylesheet"
8+
href="https://cdn.jsdelivr.net/npm/@hyprmcp/mcp-install-instructions-generator@0.1.0/dist/component/index.css"
9+
/>
10+
<style>
11+
body {
12+
background: #f0f0f0;
13+
color: #282828;
14+
margin: 0;
15+
font-family: sans-serif;
16+
}
17+
18+
main {
19+
max-width: 52rem;
20+
margin: 0 auto;
21+
}
22+
23+
.card {
24+
box-shadow:
25+
0 2px 4px rgba(0, 0, 0, 0.1),
26+
0 8px 16px rgba(0, 0, 0, 0.1);
27+
background: #fcfcfc;
28+
margin: 2.4rem 0 0.8rem;
29+
padding: 2.4rem;
30+
border-radius: 0.8rem;
31+
}
32+
33+
.hero {
34+
text-align: center;
35+
font-size: 2.4rem;
36+
font-weight: bold;
37+
}
38+
39+
.intro {
40+
margin: 2.4rem 0;
41+
}
42+
43+
h2,
44+
h3,
45+
h4,
46+
h5,
47+
h6 {
48+
margin-bottom: 0.4rem;
49+
}
50+
51+
button {
52+
background: #000000;
53+
color: #fff;
54+
border: none;
55+
border-radius: 0.4rem;
56+
padding: 0.5rem 1rem;
57+
cursor: pointer;
58+
font-weight: 500;
59+
}
60+
61+
.footer {
62+
display: flex;
63+
gap: 0.2rem;
64+
justify-content: flex-end;
65+
align-items: center;
66+
}
67+
</style>
68+
</head>
69+
<body>
70+
<main>
71+
<div class="card">
72+
<div class="hero">Install {{ or .Name .Url }}</div>
73+
<div class="intro">
74+
It looks like you're trying to install the MCP Server
75+
<strong>{{ or .Name .Url }}</strong> but you've tried opening it in
76+
your browser. If you're not sure how to proceed, don't worry, we've
77+
got you covered! Just choose your preferred AI tool below and follow
78+
the instructions:
79+
</div>
80+
<mcp-install-instructions
81+
url="{{ .Url }}"
82+
name="{{ .Name }}"
83+
></mcp-install-instructions>
84+
</div>
85+
86+
<div class="footer">
87+
Powered by
88+
<a href="https://hyprmcp.com/" target="_blank"
89+
><img src="https://hyprmcp.com/hyprmcp_20px.svg"
90+
/></a>
91+
</div>
92+
</main>
93+
</body>
94+
</html>

oauth/oauth.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"github.com/go-chi/httprate"
1313
"github.com/hyprmcp/mcp-gateway/config"
14+
"github.com/hyprmcp/mcp-gateway/htmlresponse"
1415
"github.com/hyprmcp/mcp-gateway/log"
1516
"github.com/lestrrat-go/httprc/v3"
1617
"github.com/lestrrat-go/httprc/v3/errsink"
@@ -84,10 +85,13 @@ func (mgr *Manager) Register(mux *http.ServeMux) error {
8485
}
8586

8687
func (mgr *Manager) Handler(next http.Handler) http.Handler {
88+
htmlHandler := htmlresponse.NewHandler(mgr.config)
8789
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
8890
rawToken :=
8991
strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(r.Header.Get("Authorization")), "Bearer"))
9092

93+
shouldCallNext := true
94+
9195
token, err := jwt.ParseString(rawToken, jwt.WithKeySet(mgr.jwkSet))
9296
if err != nil {
9397
metadataURL, _ := url.Parse(mgr.config.Host.String())
@@ -98,9 +102,18 @@ func (mgr *Manager) Handler(next http.Handler) http.Handler {
98102
fmt.Sprintf(`Bearer resource_metadata="%s"`, metadataURL.String()),
99103
)
100104
w.WriteHeader(http.StatusUnauthorized)
101-
return
105+
shouldCallNext = false
106+
}
107+
108+
if strings.Contains(r.Header.Get("Accept"), "text/html") {
109+
if err := htmlHandler.Handle(w, r); err != nil {
110+
log.Get(r.Context()).Error(err, "failed to handle html response")
111+
}
112+
shouldCallNext = false
102113
}
103114

104-
next.ServeHTTP(w, r.WithContext(TokenContext(r.Context(), token, rawToken)))
115+
if shouldCallNext {
116+
next.ServeHTTP(w, r.WithContext(TokenContext(r.Context(), token, rawToken)))
117+
}
105118
})
106119
}

0 commit comments

Comments
 (0)