Skip to content

Commit 9778cf9

Browse files
Make lazy-auth-server usable on mobile (#714)
The consent page shipped without a viewport meta tag, so mobile browsers laid it out at a desktop width. Its Approve/Deny buttons were 37px tall and nested a <button> inside an <a>, which is invalid HTML — interactive content is not permitted inside a link. - Add a viewport meta tag to the consent page - Replace the <a><button> nesting with anchors styled as buttons (role="button" plus a Space-key handler to keep button keyboard semantics) - Lay the actions out as a wrapping flex row with equal 56px tap targets - Add touch-action: manipulation so a tap resolves to a click immediately rather than waiting on the double-tap-to-zoom gesture - Suppress long-press text selection and the iOS callout menu on the buttons, scoped so the card's client/redirect details stay copyable - Give buttons an :active style since removing the native tap highlight otherwise leaves no press feedback - Apply the same 56px minimum and touch handling to the in-app view's buttons (the fullscreen toggle was ~33x33)
1 parent cf87f2a commit 9778cf9

2 files changed

Lines changed: 38 additions & 6 deletions

File tree

examples/lazy-auth-server/server.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -414,20 +414,36 @@ async function handleAuthorize(req: Request, res: Response) {
414414
if (state) denyUrl.searchParams.set("state", state);
415415
res.type("text/html").send(/*html*/ `<!DOCTYPE html>
416416
<html><head><meta charset="utf-8"><title>Authorize</title>
417+
<meta name="viewport" content="width=device-width, initial-scale=1">
417418
<style>body{font-family:system-ui,sans-serif;max-width:420px;margin:40px auto;padding:0 16px;color-scheme:light dark}
418-
.box{border:1px solid #ccc;border-radius:8px;padding:20px}dl{display:grid;grid-template-columns:auto 1fr;gap:6px 12px;font-size:14px;margin:16px 0}
419+
.box{border:1px solid #ccc;border-radius:8px;padding:20px}
420+
dl{display:grid;grid-template-columns:auto 1fr;gap:6px 12px;font-size:14px;margin:16px 0}
419421
dt{color:#888}dd{margin:0;word-break:break-all}
420-
button{padding:10px 20px;margin:4px;font-size:15px;font-weight:bold;border:none;border-radius:6px;cursor:pointer}
421-
.approve{background:#1a7a3e;color:#fff}.deny{background:#b91c1c;color:#fff}</style></head>
422+
.actions{display:flex;flex-wrap:wrap;gap:12px;-webkit-user-select:none;user-select:none;-webkit-touch-callout:none}
423+
.btn{box-sizing:border-box;flex:1;min-height:56px;min-width:56px;display:flex;align-items:center;justify-content:center;
424+
padding:12px 20px;font-size:16px;font-weight:bold;border:none;border-radius:8px;cursor:pointer;
425+
text-decoration:none;color:#fff;touch-action:manipulation;-webkit-tap-highlight-color:transparent}
426+
.btn:active{filter:brightness(0.8)}
427+
.approve{background:#1a7a3e}.deny{background:#b91c1c}</style></head>
422428
<body><div class="box">
423429
<h2>🔑 Mock Authorization</h2>
424430
<p>An application is requesting access:</p>
425431
<dl><dt>Client</dt><dd>${escapeHtml(client_id ?? "(none)")}</dd>
426432
<dt>Scope</dt><dd>${escapeHtml(scope ?? "(default)")}</dd>
427433
<dt>Redirect</dt><dd>${escapeHtml(redirect_uri)}</dd></dl>
428-
<a href="${escapeHtml(approveUrl.href)}"><button class="approve">Approve</button></a>
429-
<a href="${escapeHtml(denyUrl.href)}"><button class="deny">Deny</button></a>
430-
</div></body></html>`);
434+
<div class="actions">
435+
<a class="btn approve" role="button" href="${escapeHtml(approveUrl.href)}">Approve</a>
436+
<a class="btn deny" role="button" href="${escapeHtml(denyUrl.href)}">Deny</a>
437+
</div>
438+
</div>
439+
<script>
440+
// Anchors only activate on Enter; role="button" promises Space too.
441+
for (const a of document.querySelectorAll(".btn"))
442+
a.addEventListener("keydown", (e) => {
443+
if (e.key === " ") { e.preventDefault(); a.click(); }
444+
});
445+
</script>
446+
</body></html>`);
431447
return;
432448
}
433449

examples/lazy-auth-server/src/mcp-app.css

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,38 @@
1717
display: flex;
1818
flex-wrap: wrap;
1919
gap: var(--spacing-sm);
20+
/* Keep a long press on or near the buttons from starting text selection
21+
or raising the iOS callout menu instead of a click. */
22+
-webkit-user-select: none;
23+
user-select: none;
24+
-webkit-touch-callout: none;
2025
}
2126

2227
button {
28+
/* Match the consent page's 56px tap targets (server.ts .btn). */
29+
min-height: 56px;
30+
min-width: 56px;
2331
padding: var(--spacing-sm) var(--spacing-md);
2432
border: none;
2533
border-radius: var(--border-radius-md);
2634
color: var(--color-text-on-accent);
2735
font-weight: var(--font-weight-bold);
2836
background-color: var(--color-accent);
2937
cursor: pointer;
38+
/* Suppress the double-tap-to-zoom gesture so a tap resolves to a click
39+
immediately instead of waiting to see if a second tap follows. */
40+
touch-action: manipulation;
41+
-webkit-tap-highlight-color: transparent;
3042

3143
&:disabled {
3244
opacity: 0.5;
3345
cursor: not-allowed;
3446
}
3547

48+
&:active:not(:disabled) {
49+
filter: brightness(0.85);
50+
}
51+
3652
&:hover:not(:disabled) {
3753
background-color: color-mix(
3854
in srgb,

0 commit comments

Comments
 (0)