fix(review): loop-2 findings — login fallback, proof-band confidence, contrast, content hedges
Code review: login no longer strands visitors with zero sign-in methods when
the providers fetch fails (falls back to SMS + error notice); skeleton while
providers load instead of a blank card.
Design re-audit: proof band flipped from apology to flex ('Don't take our
word for it / Every claim links to its proof') and promoted to the primary
heading tier; unverifiable '60 seconds'/'in minutes' speed claims dropped;
fg-subtle body text bumped to fg-muted (AA contrast); marketplace section
differentiated via border-y + elevated bg (adjacent hairlines removed);
preview frame traffic lights on-palette; header h-12 -> h-14 (hero svh calc
adjusted); emerald-400 -> --color-success token.
Content QA: rest-api article description drift between page and registry
resolved; ChatGPT plan-gating table and Atlassian Rovo SSE-cutoff claims now
carry dated hedges.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SXUwmPVRTD8AKQtio6gCN5
This commit is contained in:
parent
a08f5f05b1
commit
c4f0db5719
@ -38,7 +38,9 @@ export default function Page() {
|
||||
<H2>The plan limits, first — because they decide everything</H2>
|
||||
<P>
|
||||
Before writing a single prompt or line of code, check what your ChatGPT plan allows.
|
||||
OpenAI gates custom MCP connectors by workspace type:
|
||||
As of mid-2026, OpenAI gates custom MCP connectors by workspace type — verify against
|
||||
OpenAI's current help docs before committing to a plan, since these limits have
|
||||
shifted before:
|
||||
</P>
|
||||
<Table>
|
||||
<thead>
|
||||
|
||||
@ -129,7 +129,8 @@ export default function Page() {
|
||||
Spec revision <Strong>2025-03-26</Strong> replaced HTTP+SSE with Streamable HTTP. The old
|
||||
transport still works where both sides keep supporting it, but the direction is one-way:
|
||||
platforms have been announcing removal dates through 2026 — Atlassian's Rovo MCP server,
|
||||
for example, set its HTTP+SSE cutoff for June 30, 2026. New servers should not ship it.
|
||||
for example, announced an HTTP+SSE cutoff of June 30, 2026. New servers should not ship
|
||||
it.
|
||||
</P>
|
||||
|
||||
<H2>Streamable HTTP: the current standard</H2>
|
||||
|
||||
@ -8,7 +8,7 @@ export default function MarketingLayout({ children }: { children: React.ReactNod
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col">
|
||||
<header className="sticky top-0 z-50 border-b border-[--color-border] bg-[--color-bg]/80 backdrop-blur-md">
|
||||
<div className="mx-auto flex h-12 max-w-6xl items-center justify-between px-5 sm:px-6">
|
||||
<div className="mx-auto flex h-14 max-w-6xl items-center justify-between px-5 sm:px-6">
|
||||
<div className="flex items-center gap-6">
|
||||
<Logo />
|
||||
<nav className="hidden items-center gap-5 text-[13px] text-[--color-fg-muted] md:flex">
|
||||
@ -45,15 +45,15 @@ export default function MarketingLayout({ children }: { children: React.ReactNod
|
||||
{/* Brand column — positioning line + live status. */}
|
||||
<div className="sm:col-span-2 md:col-span-1">
|
||||
<Logo />
|
||||
<p className="mt-3 max-w-xs text-[12.5px] leading-relaxed text-[--color-fg-subtle]">
|
||||
<p className="mt-3 max-w-xs text-[12.5px] leading-relaxed text-[--color-fg-muted]">
|
||||
From a prompt to a hosted, OAuth-protected MCP server — for Claude, Cursor and
|
||||
ChatGPT.
|
||||
</p>
|
||||
<Link
|
||||
href="/status"
|
||||
className="mt-4 flex items-center gap-2 text-[12px] text-[--color-fg-subtle] transition-colors hover:text-[--color-fg]"
|
||||
className="mt-4 flex items-center gap-2 text-[12px] text-[--color-fg-muted] transition-colors hover:text-[--color-fg]"
|
||||
>
|
||||
<span className="size-1.5 animate-pulse rounded-full bg-emerald-400" />
|
||||
<span className="size-1.5 animate-pulse rounded-full bg-[--color-success]" />
|
||||
<span>System status</span>
|
||||
</Link>
|
||||
</div>
|
||||
@ -107,7 +107,7 @@ function FooterColumn({
|
||||
<h3 className="text-[11px] font-semibold uppercase tracking-[0.16em] text-[--color-fg-muted]">
|
||||
{title}
|
||||
</h3>
|
||||
<ul className="mt-3 space-y-2 text-[12.5px] text-[--color-fg-subtle]">
|
||||
<ul className="mt-3 space-y-2 text-[12.5px] text-[--color-fg-muted]">
|
||||
{links.map((l) => (
|
||||
<li key={l.href}>
|
||||
<Link href={l.href} className="transition-colors hover:text-[--color-fg]">
|
||||
|
||||
@ -164,7 +164,7 @@ export default function Landing() {
|
||||
CTAs stay interactive. */}
|
||||
<section
|
||||
className="relative flex items-center overflow-hidden border-b border-[--color-border]"
|
||||
style={{ minHeight: 'calc(100svh - 3rem)' }}
|
||||
style={{ minHeight: 'calc(100svh - 3.5rem)' }}
|
||||
>
|
||||
<ParticleHero />
|
||||
<div className="relative z-10 mx-auto grid w-full max-w-6xl gap-10 px-6 py-14 sm:py-20 md:grid-cols-[1.05fr_1fr] md:items-center md:gap-12">
|
||||
@ -177,8 +177,8 @@ export default function Landing() {
|
||||
<span className="text-[--color-fg-muted]">AI uses it.</span>
|
||||
</h1>
|
||||
<p className="mt-5 max-w-md text-[15px] leading-relaxed text-[--color-fg-muted] sm:text-[16px]">
|
||||
From prompt to production MCP server in 60 seconds. OAuth 2.1, Streamable HTTP, ready
|
||||
for Claude, Cursor and ChatGPT.
|
||||
From a prompt to a production MCP server — OAuth 2.1, Streamable HTTP, ready for
|
||||
Claude, Cursor and ChatGPT.
|
||||
</p>
|
||||
<div className="mt-8 flex flex-wrap items-center gap-3">
|
||||
<PulseLink
|
||||
@ -191,21 +191,21 @@ export default function Landing() {
|
||||
href="#flow"
|
||||
className="inline-flex h-11 items-center justify-center rounded-md border border-[--color-border] bg-[--color-bg-elevated] px-5 text-[14px] text-[--color-fg-muted] transition-colors hover:text-[--color-fg]"
|
||||
>
|
||||
See a live build
|
||||
Watch a build
|
||||
</PulseLink>
|
||||
</div>
|
||||
<div className="mt-8 flex flex-wrap gap-x-6 gap-y-2 text-[12px] text-[--color-fg-subtle]">
|
||||
<div className="mt-8 flex flex-wrap gap-x-6 gap-y-2 text-[12px] text-[--color-fg-muted]">
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
<span className="size-1.5 rounded-full bg-emerald-400" /> OAuth 2.1 + PKCE
|
||||
<span className="size-1.5 rounded-full bg-[--color-success]" /> OAuth 2.1 + PKCE
|
||||
</span>
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
<span className="size-1.5 rounded-full bg-emerald-400" /> Streamable HTTP
|
||||
<span className="size-1.5 rounded-full bg-[--color-success]" /> Streamable HTTP
|
||||
</span>
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
<span className="size-1.5 rounded-full bg-emerald-400" /> AES-256 secrets
|
||||
<span className="size-1.5 rounded-full bg-[--color-success]" /> AES-256 secrets
|
||||
</span>
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
<span className="size-1.5 rounded-full bg-emerald-400" /> Per-server isolation
|
||||
<span className="size-1.5 rounded-full bg-[--color-success]" /> Per-server isolation
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -290,13 +290,13 @@ export default function Landing() {
|
||||
{/* Proof by specificity — three verifiable claims, each one click from
|
||||
its evidence. Replaces the old pseudo-logo row: no invented marks,
|
||||
just a plain-text compatibility line. */}
|
||||
<section className="border-b border-[--color-border] py-16 sm:py-24">
|
||||
<section className="py-16 sm:py-24">
|
||||
<div className="mx-auto max-w-6xl px-6">
|
||||
<Kicker>verify_it_yourself</Kicker>
|
||||
<h2 className="mt-3 max-w-2xl text-[28px] font-semibold leading-[1.1] tracking-tight sm:text-[32px]">
|
||||
No testimonials yet — we're new.
|
||||
<h2 className="mt-3 max-w-2xl text-[32px] font-semibold leading-[1.1] tracking-tight sm:text-[40px]">
|
||||
Don't take our word for it.
|
||||
<br />
|
||||
<span className="text-[--color-fg-muted]">So check the claims instead.</span>
|
||||
<span className="text-[--color-fg-muted]">Every claim links to its proof.</span>
|
||||
</h2>
|
||||
<div className="mt-10 grid gap-4 md:grid-cols-3">
|
||||
{PROOF_POINTS.map((p) => {
|
||||
@ -319,15 +319,16 @@ export default function Landing() {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<p className="mt-10 text-center text-[13px] text-[--color-fg-subtle]">
|
||||
<p className="mt-10 text-center text-[13px] text-[--color-fg-muted]">
|
||||
Works with Claude Desktop, Cursor, ChatGPT, VS Code Copilot and Continue.dev — anything
|
||||
that speaks MCP.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Use cases — brand-coloured integration grid. */}
|
||||
<section className="border-b border-[--color-border] py-20 sm:py-28">
|
||||
{/* Use cases — brand-coloured integration grid. No bottom hairline:
|
||||
the marketplace section below brings its own border-y. */}
|
||||
<section className="py-20 sm:py-28">
|
||||
<div className="mx-auto max-w-6xl px-6">
|
||||
<div className="mb-12 grid gap-6 md:grid-cols-[1fr_auto] md:items-end md:gap-12">
|
||||
<div>
|
||||
@ -335,11 +336,11 @@ export default function Landing() {
|
||||
<h2 className="mt-3 text-[32px] font-semibold leading-[1.1] tracking-tight sm:text-[40px]">
|
||||
Wrap any HTTP API.
|
||||
<br />
|
||||
<span className="text-[--color-fg-muted]">In minutes.</span>
|
||||
<span className="text-[--color-fg-muted]">From one prompt.</span>
|
||||
</h2>
|
||||
</div>
|
||||
<p className="max-w-xs text-[14px] leading-relaxed text-[--color-fg-muted]">
|
||||
Ship integrations like these from one prompt each.
|
||||
Each shipped from a single prompt.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@ -375,8 +376,9 @@ export default function Landing() {
|
||||
</section>
|
||||
|
||||
{/* Marketplace — split layout: selling points left, honest preview
|
||||
frame right. */}
|
||||
<section className="border-b border-[--color-border] py-20 sm:py-28">
|
||||
frame right. Elevated background (no hairline) so the page reads
|
||||
in blocks instead of one continuously ruled ledger. */}
|
||||
<section className="border-y border-[--color-border] bg-[--color-bg-elevated] py-20 sm:py-28">
|
||||
<div className="mx-auto grid max-w-6xl gap-14 px-6 md:grid-cols-[5fr_6fr] md:items-center md:gap-16">
|
||||
<div>
|
||||
<Kicker>marketplace</Kicker>
|
||||
@ -602,9 +604,9 @@ function MarketplacePreview() {
|
||||
{/* Browser chrome */}
|
||||
<div className="flex items-center gap-3 border-b border-[--color-border] bg-[--color-bg-subtle] px-4 py-3">
|
||||
<div className="flex gap-1.5">
|
||||
<span className="size-2.5 rounded-full bg-zinc-700" />
|
||||
<span className="size-2.5 rounded-full bg-zinc-700" />
|
||||
<span className="size-2.5 rounded-full bg-zinc-700" />
|
||||
<span className="size-2.5 rounded-full bg-[#ff5f57]/80" />
|
||||
<span className="size-2.5 rounded-full bg-[#febc2e]/80" />
|
||||
<span className="size-2.5 rounded-full bg-[#28c840]/80" />
|
||||
</div>
|
||||
<div className="mono flex-1 truncate rounded-md border border-[--color-border] bg-[--color-bg] px-3 py-1 text-[11px] text-[--color-fg-subtle]">
|
||||
buildmymcpserver.com/templates
|
||||
|
||||
@ -208,6 +208,7 @@ export default function LoginPage() {
|
||||
// product should never see a phone-number field as the front door. It only
|
||||
// renders expanded when SMS is the sole configured provider.
|
||||
const [phoneOpen, setPhoneOpen] = useState(false);
|
||||
const [providersState, setProvidersState] = useState<'loading' | 'ready' | 'failed'>('loading');
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Email magic-link
|
||||
@ -228,11 +229,21 @@ export default function LoginPage() {
|
||||
)
|
||||
.then((p) => {
|
||||
setProviders(p);
|
||||
setProvidersState('ready');
|
||||
// SMS as the only provider → show the phone form directly (no point
|
||||
// hiding the sole sign-in method behind a toggle).
|
||||
if (p.sms && !p.email && !p.google && !p.github) setPhoneOpen(true);
|
||||
})
|
||||
.catch(() => undefined);
|
||||
.catch(() => {
|
||||
// If the providers lookup fails, the page must not strand the visitor
|
||||
// with zero sign-in methods — fall back to SMS + phone form expanded
|
||||
// (the one flow that has no provider-specific client config) and say
|
||||
// why the other options are missing.
|
||||
setProviders({ google: false, github: false, sms: true, email: false });
|
||||
setPhoneOpen(true);
|
||||
setProvidersState('failed');
|
||||
setError('Some sign-in options could not be loaded. Reload the page to try again.');
|
||||
});
|
||||
const err = new URLSearchParams(window.location.search).get('error');
|
||||
if (err) setError(ERROR_COPY[err] ?? 'Sign-in failed. Please try again.');
|
||||
}, []);
|
||||
@ -296,6 +307,15 @@ export default function LoginPage() {
|
||||
Passwordless — pick whichever is easiest.
|
||||
</p>
|
||||
|
||||
{/* Providers still loading → neutral skeleton instead of a blank card
|
||||
(previously nothing rendered until the fetch resolved). */}
|
||||
{providersState === 'loading' && (
|
||||
<div aria-hidden className="mt-7 space-y-2">
|
||||
<div className="h-10 w-full animate-pulse rounded-md bg-[--color-bg-elevated]" />
|
||||
<div className="h-10 w-full animate-pulse rounded-md bg-[--color-bg-elevated]" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{hasOAuth && (
|
||||
<div className="mt-7 space-y-2">
|
||||
{providers.google && (
|
||||
|
||||
@ -43,7 +43,7 @@ export const ARTICLES: Article[] = [
|
||||
slug: 'rest-api-to-mcp-server',
|
||||
title: 'Wrap any REST API as an MCP server',
|
||||
description:
|
||||
'How to turn a REST API into an MCP server your AI clients can use: designing the tool surface, handling auth headers with encrypted secrets, and respecting upstream rate limits.',
|
||||
'How to turn a REST API into an MCP server your AI clients can use: designing the tool surface (fewer, better tools beat 1:1 endpoint mapping), handling auth headers with encrypted secrets, and respecting upstream rate limits.',
|
||||
tag: 'Guide',
|
||||
datePublished: '2026-07-08',
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user