import { JsonLd } from '@/components/json-ld';
import { articleJsonLd, breadcrumbJsonLd, 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',
        })}
      />
      <JsonLd
        data={breadcrumbJsonLd([
          { name: 'Home', path: '/' },
          { name: 'Guides', path: '/guides' },
          { name: TITLE, path: PATH },
        ])}
      />
      <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 &quot;confidential&quot; 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 &quot;working&quot; 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>
    </>
  );
}
