Skip to content

Commit d8e706f

Browse files
Create beautiful black and white MCP-branded auth pages
**MCP Server Auth Page (src/auth/provider.ts):** - Black background with white card design - Official MCP logo and branding - Clear client information display - Elegant "Continue to Authentication" flow - Professional MCP server identification **Upstream Fake Auth Page (src/handlers/fakeauth.ts):** - Updated branding to distinguish from MCP server - Lock icon and "Upstream Authentication" title - Clear user ID management with localStorage - Generate/edit user ID functionality for testing - Multi-user testing instructions **Technical Updates:** - Permissive CSP headers for both auth pages - Logo serving route at /mcp-logo.png - Responsive design with modern CSS - Clear visual distinction between MCP server auth vs upstream auth - Enhanced userId integration in OAuth authorization flow Both pages now provide professional user experience while maintaining clear distinction between the MCP server's auth and the fake upstream provider auth for testing purposes. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 20d722a commit d8e706f

File tree

7 files changed

+447
-22
lines changed

7 files changed

+447
-22
lines changed

mcp.png

39.7 KB
Loading

src/auth/provider.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ function getMockAuthValues() {
5353
},
5454
clientId: client.client_id,
5555
issuedAt: Date.now() / 1000,
56+
userId: "test-user-id",
5657
};
5758

5859
return {
@@ -279,6 +280,7 @@ describe("EverythingAuthProvider", () => {
279280
},
280281
clientId: "different-client-id",
281282
issuedAt: Date.now() / 1000,
283+
userId: "test-user-id",
282284
};
283285

284286
await authService.saveRefreshToken(refreshToken, accessToken);
@@ -314,6 +316,7 @@ describe("EverythingAuthProvider", () => {
314316
},
315317
clientId: "client-id",
316318
issuedAt: Date.now() / 1000,
319+
userId: "test-user-id",
317320
};
318321

319322
await authService.saveMcpInstallation(accessToken, mcpInstallation);
@@ -351,6 +354,7 @@ describe("EverythingAuthProvider", () => {
351354
},
352355
clientId: "client-id",
353356
issuedAt: twoDaysAgoInSeconds, // 2 days ago, with 1-day expiry
357+
userId: "test-user-id",
354358
};
355359

356360
await authService.saveMcpInstallation(accessToken, mcpInstallation);
@@ -377,6 +381,7 @@ describe("EverythingAuthProvider", () => {
377381
},
378382
clientId: client.client_id,
379383
issuedAt: Date.now() / 1000,
384+
userId: "test-user-id",
380385
};
381386

382387
// Save the installation

src/auth/provider.ts

Lines changed: 191 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -72,22 +72,196 @@ export class EverythingAuthProvider implements OAuthServerProvider {
7272
// You can redirect to another page, or you can send an html response directly
7373
// res.redirect(new URL(`fakeupstreamauth/authorize?metadata=${authorizationCode}`, BASE_URI).href);
7474

75+
// Set permissive CSP for styling
76+
res.setHeader('Content-Security-Policy', [
77+
"default-src 'self'",
78+
"style-src 'self' 'unsafe-inline'",
79+
"script-src 'self' 'unsafe-inline'",
80+
"img-src 'self' data:",
81+
"object-src 'none'",
82+
"frame-ancestors 'none'",
83+
"form-action 'self'",
84+
"base-uri 'self'"
85+
].join('; '));
86+
7587
res.send(`
76-
<html>
88+
<!DOCTYPE html>
89+
<html lang="en">
7790
<head>
78-
<title>MCP Auth Page</title>
91+
<meta charset="UTF-8">
92+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
93+
<title>MCP Server Authorization</title>
94+
<style>
95+
* {
96+
margin: 0;
97+
padding: 0;
98+
box-sizing: border-box;
99+
}
100+
101+
body {
102+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
103+
background: #000000;
104+
color: #ffffff;
105+
min-height: 100vh;
106+
display: flex;
107+
align-items: center;
108+
justify-content: center;
109+
padding: 20px;
110+
}
111+
112+
.auth-container {
113+
background: #ffffff;
114+
color: #000000;
115+
border-radius: 16px;
116+
box-shadow: 0 20px 40px rgba(255, 255, 255, 0.1);
117+
padding: 40px;
118+
max-width: 500px;
119+
width: 100%;
120+
text-align: center;
121+
border: 1px solid #e2e8f0;
122+
}
123+
124+
.logo-container {
125+
margin-bottom: 32px;
126+
}
127+
128+
.logo {
129+
width: 80px;
130+
height: 80px;
131+
margin: 0 auto 16px;
132+
filter: invert(1);
133+
}
134+
135+
.mcp-title {
136+
font-size: 24px;
137+
font-weight: 700;
138+
color: #000000;
139+
margin-bottom: 8px;
140+
letter-spacing: 2px;
141+
}
142+
143+
h1 {
144+
color: #000000;
145+
font-size: 32px;
146+
font-weight: 800;
147+
margin-bottom: 12px;
148+
line-height: 1.2;
149+
}
150+
151+
.subtitle {
152+
color: #4a5568;
153+
font-size: 18px;
154+
margin-bottom: 32px;
155+
line-height: 1.5;
156+
}
157+
158+
.client-info {
159+
background: #f8f9fa;
160+
border-radius: 12px;
161+
padding: 24px;
162+
margin-bottom: 32px;
163+
border: 2px solid #e2e8f0;
164+
}
165+
166+
.client-info h3 {
167+
color: #2d3748;
168+
font-size: 18px;
169+
font-weight: 600;
170+
margin-bottom: 16px;
171+
}
172+
173+
.client-id {
174+
background: white;
175+
border: 2px solid #e2e8f0;
176+
border-radius: 8px;
177+
padding: 12px;
178+
font-family: 'Courier New', monospace;
179+
font-size: 14px;
180+
color: #4a5568;
181+
word-break: break-all;
182+
}
183+
184+
.auth-flow-info {
185+
background: #f8f9fa;
186+
border-radius: 12px;
187+
padding: 24px;
188+
margin-bottom: 32px;
189+
border-left: 4px solid #000000;
190+
}
191+
192+
.auth-flow-info h3 {
193+
color: #2d3748;
194+
font-size: 18px;
195+
font-weight: 600;
196+
margin-bottom: 12px;
197+
}
198+
199+
.auth-flow-info p {
200+
color: #4a5568;
201+
font-size: 14px;
202+
line-height: 1.5;
203+
}
204+
205+
.btn-primary {
206+
background: #000000;
207+
color: #ffffff;
208+
font-size: 18px;
209+
font-weight: 700;
210+
padding: 18px 36px;
211+
border: none;
212+
border-radius: 8px;
213+
cursor: pointer;
214+
transition: all 0.2s ease;
215+
width: 100%;
216+
text-decoration: none;
217+
display: inline-block;
218+
text-align: center;
219+
letter-spacing: 1px;
220+
}
221+
222+
.btn-primary:hover {
223+
background: #333333;
224+
transform: translateY(-2px);
225+
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
226+
}
227+
228+
.branding {
229+
margin-top: 24px;
230+
padding-top: 24px;
231+
border-top: 1px solid #e2e8f0;
232+
color: #718096;
233+
font-size: 12px;
234+
}
235+
</style>
79236
</head>
80237
<body>
81-
<h1>MCP Server Auth Page</h1>
82-
<p>
83-
This page is the authorization page presented by the MCP server, routing the user upstream. This is only
84-
needed on 2025-03-26 Auth spec, where the MCP server acts as it's own authoriztion server. This page should
85-
be present to avoid confused deputy attacks.
86-
</p>
87-
<p>
88-
Click <a href="/fakeupstreamauth/authorize?redirect_uri=/fakeupstreamauth/callback&state=${authorizationCode}">here</a>
89-
to continue to the upstream auth
90-
</p>
238+
<div class="auth-container">
239+
<div class="logo-container">
240+
<img src="/mcp-logo.png" alt="MCP Logo" class="logo">
241+
<div class="mcp-title">MCP</div>
242+
</div>
243+
244+
<h1>Authorization Required</h1>
245+
<p class="subtitle">This client wants to connect to your MCP server</p>
246+
247+
<div class="client-info">
248+
<h3>Client Application</h3>
249+
<div class="client-id">${client.client_id}</div>
250+
</div>
251+
252+
<div class="auth-flow-info">
253+
<h3>What happens next?</h3>
254+
<p>You'll be redirected to authenticate with the upstream provider. Once verified, you'll be granted access to this MCP server's resources.</p>
255+
</div>
256+
257+
<a href="/fakeupstreamauth/authorize?redirect_uri=/fakeupstreamauth/callback&state=${authorizationCode}" class="btn-primary">
258+
Continue to Authentication
259+
</a>
260+
261+
<div class="branding">
262+
Model Context Protocol (MCP) Server
263+
</div>
264+
</div>
91265
</body>
92266
</html>
93267
`);
@@ -156,6 +330,7 @@ export class EverythingAuthProvider implements OAuthServerProvider {
156330
...mcpInstallation,
157331
mcpTokens: newTokens,
158332
issuedAt: Date.now() / 1000,
333+
userId: mcpInstallation.userId, // Preserve the user ID
159334
});
160335

161336
return newTokens;
@@ -183,7 +358,10 @@ export class EverythingAuthProvider implements OAuthServerProvider {
183358
token,
184359
clientId: installation.clientId,
185360
scopes: ['mcp'],
186-
expiresAt
361+
expiresAt,
362+
extra: {
363+
userId: installation.userId
364+
}
187365
};
188366
}
189367

0 commit comments

Comments
 (0)