feat(web): SEO — server-rendered template pages + /guides articles - templates/[slug] converted from client to server component: per-template generateMetadata (title/description/canonical/OG) + SoftwareApplication JSON-LD; code-audit toggle split into a client island; missing/non-public templates now return a real 404. - sitemap.ts pulls public template slugs live from the API (best-effort) + the new /guides routes. - new /guides section: 3 server-rendered SEO articles (host MCP with OAuth, hosted-platforms comparison, MintMCP alternative) with TechArticle JSON-LD; Guides link added to the marketing nav. - lib/seo.ts: articleJsonLd + templateJsonLd builders. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> @
117 lines
5.8 KiB
XML
117 lines
5.8 KiB
XML
import { JsonLd } from '@/components/json-ld';
|
|
import { articleJsonLd, pageMetadata } from '@/lib/seo';
|
|
import Link from 'next/link';
|
|
import { ArticleShell, H2, P, Strong, UL } from '../article-shell';
|
|
|
|
const PATH = '/guides/host-mcp-server-with-oauth';
|
|
const TITLE = 'How to host a remote MCP server with OAuth (2026)';
|
|
const DESCRIPTION =
|
|
'What it actually takes to put a remote MCP server in production: Streamable HTTP transport, OAuth 2.1 with PKCE and Resource Indicators, and the shortcuts.';
|
|
|
|
export const metadata = pageMetadata({ title: TITLE, description: DESCRIPTION, path: PATH });
|
|
|
|
export default function Page() {
|
|
return (
|
|
<>
|
|
<JsonLd
|
|
data={articleJsonLd({
|
|
title: TITLE,
|
|
description: DESCRIPTION,
|
|
path: PATH,
|
|
datePublished: '2026-05-31',
|
|
})}
|
|
/>
|
|
<ArticleShell
|
|
title={TITLE}
|
|
subtitle="Local STDIO servers are easy. A remote MCP server that Claude, Cursor and ChatGPT can install over the internet — without leaving it open to the world — is where the real work is. Here's the whole picture."
|
|
updated="May 2026"
|
|
>
|
|
<H2>Local vs remote: why this is harder than it looks</H2>
|
|
<P>
|
|
A local MCP server talks to one client over STDIO on your machine — no network, no auth.
|
|
The moment you want a server that lives at a URL and any MCP client can connect to, you
|
|
inherit a full web-service problem: a public transport, TLS, identity, authorization, and
|
|
isolation between callers. The MCP spec settled on <Strong>Streamable HTTP</Strong> as the
|
|
remote transport (it replaced the older HTTP+SSE pairing), and on{' '}
|
|
<Strong>OAuth 2.1</Strong> as the auth model. Both are non-negotiable if you want the
|
|
server installable from Claude Desktop or ChatGPT.
|
|
</P>
|
|
|
|
<H2>The OAuth 2.1 pieces you can't skip</H2>
|
|
<P>
|
|
MCP authorization is OAuth 2.1, and for remote servers it leans on a few RFCs that older
|
|
OAuth tutorials don't cover:
|
|
</P>
|
|
<UL>
|
|
<li>
|
|
<Strong>PKCE (RFC 7636)</Strong> on every authorization-code exchange — mandatory in
|
|
OAuth 2.1, no exceptions for "confidential" clients.
|
|
</li>
|
|
<li>
|
|
<Strong>Dynamic Client Registration (RFC 7591)</Strong> — clients like Claude Desktop
|
|
register themselves at runtime; you can't pre-provision a client_id for every user.
|
|
</li>
|
|
<li>
|
|
<Strong>Resource Indicators (RFC 8707)</Strong> — the token has to be bound to the
|
|
specific MCP server (the <code>resource</code>), so a token minted for one server can't
|
|
be replayed against another.
|
|
</li>
|
|
<li>
|
|
<Strong>Protected-resource metadata</Strong> — your server returns a{' '}
|
|
<code>WWW-Authenticate</code> header pointing at the authorization server so clients can
|
|
discover where to get a token.
|
|
</li>
|
|
</UL>
|
|
<P>
|
|
Get any of these wrong and the symptom is the same: the client either can't complete the
|
|
handshake, or it silently fails to discover your auth server. This is the single most
|
|
common reason a "working" MCP server won't install from Claude.
|
|
</P>
|
|
|
|
<H2>Option A — roll your own on generic infra</H2>
|
|
<P>
|
|
You can deploy a remote MCP server to <Strong>Cloudflare Workers</Strong> (the most common
|
|
production choice, edge-global), or to <Strong>Render, Fly, or Cloud Run</Strong> as a
|
|
normal container. Cloudflare even ships an OAuth provider library for Workers-based MCP
|
|
servers. This path gives you full control and is the right call if you have engineers and
|
|
want to own the runtime.
|
|
</P>
|
|
<P>
|
|
The cost is everything around the code: standing up the authorization server (or wiring a
|
|
third-party IdP correctly for the RFCs above), per-tenant secret storage, TLS, rate
|
|
limiting, and keeping the transport spec-current as MCP evolves. Budget days, not hours,
|
|
for the auth layer alone.
|
|
</P>
|
|
|
|
<H2>Option B — a platform that wraps it for you</H2>
|
|
<P>
|
|
If you already have a server, tools like <Strong>MintMCP</Strong> take a local STDIO
|
|
server and expose it as a remote one with OAuth wrapping. If you{' '}
|
|
<Strong>don't have a server yet</Strong>, that's where BuildMyMCPServer fits: you describe
|
|
the tool in plain language, it generates the TypeScript MCP server, runs static checks,
|
|
builds a container, and deploys it behind a full OAuth 2.1 authorization server — PKCE,
|
|
DCR and Resource Indicators included — with copy-paste install snippets for each client.
|
|
</P>
|
|
|
|
<H2>A practical checklist before you ship</H2>
|
|
<UL>
|
|
<li>Transport is Streamable HTTP, served over TLS at a stable public URL.</li>
|
|
<li>Unauthenticated request returns 401 + a <code>WWW-Authenticate</code> pointing at your AS.</li>
|
|
<li>Authorization code flow enforces PKCE (S256), exact redirect-URI match, single-use codes.</li>
|
|
<li>Issued access tokens are audience-bound to the specific server (RFC 8707).</li>
|
|
<li>Per-caller secrets are encrypted at rest and injected only at runtime, never logged.</li>
|
|
<li>You've actually installed it from Claude Desktop end-to-end — not just curl'd it.</li>
|
|
</UL>
|
|
|
|
<P>
|
|
Whichever route you take, test the real install path in a real client early. See the{' '}
|
|
<Link href="/guides/hosted-mcp-platforms-compared" className="text-[--color-accent] hover:underline">
|
|
platform comparison
|
|
</Link>{' '}
|
|
for which option fits your situation.
|
|
</P>
|
|
</ArticleShell>
|
|
</>
|
|
);
|
|
}
|