diff --git a/apps/web/app/(marketing)/guides/article-shell.tsx b/apps/web/app/(marketing)/guides/article-shell.tsx
new file mode 100644
index 0000000..e4e5823
--- /dev/null
+++ b/apps/web/app/(marketing)/guides/article-shell.tsx
@@ -0,0 +1,73 @@
+import Link from 'next/link';
+import type { ReactNode } from 'react';
+
+// Shared layout + typographic primitives for /guides/* SEO articles. Server
+// component (no client JS) so each article page can export its own metadata.
+
+export function ArticleShell({
+ title,
+ subtitle,
+ updated,
+ children,
+}: {
+ title: string;
+ subtitle?: string;
+ updated?: string;
+ children: ReactNode;
+}) {
+ return (
+
+
+ ← Guides
+
+
+ {title}
+
+ {subtitle &&
{subtitle}
}
+ {updated &&
Updated {updated}
}
+
{children}
+
+
+
+ Skip the boilerplate — describe your tool, get a hosted MCP server.
+
+
+ BuildMyMCPServer generates the TypeScript server, wraps it in OAuth 2.1 and deploys it to a
+ public Streamable HTTP URL for Claude, Cursor and ChatGPT. Free tier, source export, no
+ lock-in.
+
+
+ Start building →
+
+
+
+ );
+}
+
+export function H2({ children }: { children: ReactNode }) {
+ return (
+
{children}
+ );
+}
+
+export function P({ children }: { children: ReactNode }) {
+ return
{children}
;
+}
+
+export function UL({ children }: { children: ReactNode }) {
+ return (
+
+ {children}
+
+ );
+}
+
+export function Strong({ children }: { children: ReactNode }) {
+ return {children};
+}
diff --git a/apps/web/app/(marketing)/guides/host-mcp-server-with-oauth/page.tsx b/apps/web/app/(marketing)/guides/host-mcp-server-with-oauth/page.tsx
new file mode 100644
index 0000000..1aa8361
--- /dev/null
+++ b/apps/web/app/(marketing)/guides/host-mcp-server-with-oauth/page.tsx
@@ -0,0 +1,116 @@
+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 (
+ <>
+
+
+
Local vs remote: why this is harder than it looks
+
+ 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 Streamable HTTP as the
+ remote transport (it replaced the older HTTP+SSE pairing), and on{' '}
+ OAuth 2.1 as the auth model. Both are non-negotiable if you want the
+ server installable from Claude Desktop or ChatGPT.
+
+
+
The OAuth 2.1 pieces you can't skip
+
+ MCP authorization is OAuth 2.1, and for remote servers it leans on a few RFCs that older
+ OAuth tutorials don't cover:
+
+
+
+ PKCE (RFC 7636) on every authorization-code exchange — mandatory in
+ OAuth 2.1, no exceptions for "confidential" clients.
+
+
+ Dynamic Client Registration (RFC 7591) — clients like Claude Desktop
+ register themselves at runtime; you can't pre-provision a client_id for every user.
+
+
+ Resource Indicators (RFC 8707) — the token has to be bound to the
+ specific MCP server (the resource), so a token minted for one server can't
+ be replayed against another.
+
+
+ Protected-resource metadata — your server returns a{' '}
+ WWW-Authenticate header pointing at the authorization server so clients can
+ discover where to get a token.
+
+
+
+ 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.
+
+
+
Option A — roll your own on generic infra
+
+ You can deploy a remote MCP server to Cloudflare Workers (the most common
+ production choice, edge-global), or to Render, Fly, or Cloud Run 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.
+
+
+ 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.
+
+
+
Option B — a platform that wraps it for you
+
+ If you already have a server, tools like MintMCP take a local STDIO
+ server and expose it as a remote one with OAuth wrapping. If you{' '}
+ don't have a server yet, 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.
+
+
+
A practical checklist before you ship
+
+
Transport is Streamable HTTP, served over TLS at a stable public URL.
+
Unauthenticated request returns 401 + a WWW-Authenticate pointing at your AS.
Issued access tokens are audience-bound to the specific server (RFC 8707).
+
Per-caller secrets are encrypted at rest and injected only at runtime, never logged.
+
You've actually installed it from Claude Desktop end-to-end — not just curl'd it.
+
+
+
+ Whichever route you take, test the real install path in a real client early. See the{' '}
+
+ platform comparison
+ {' '}
+ for which option fits your situation.
+
+
+ >
+ );
+}
diff --git a/apps/web/app/(marketing)/guides/hosted-mcp-platforms-compared/page.tsx b/apps/web/app/(marketing)/guides/hosted-mcp-platforms-compared/page.tsx
new file mode 100644
index 0000000..f13b4e9
--- /dev/null
+++ b/apps/web/app/(marketing)/guides/hosted-mcp-platforms-compared/page.tsx
@@ -0,0 +1,110 @@
+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/hosted-mcp-platforms-compared';
+const TITLE =
+ 'Hosted MCP platforms compared: Cloudflare, Smithery, Composio & generating your own';
+const DESCRIPTION =
+ 'The MCP hosting landscape splits into four categories — registries, connector platforms, hosting infra, and generators. Here is which one fits which job.';
+
+export const metadata = pageMetadata({ title: TITLE, description: DESCRIPTION, path: PATH });
+
+export default function Page() {
+ return (
+ <>
+
+
+
1. Registries & directories
+
+ Smithery, Glama and PulseMCP are about{' '}
+ discovery — finding and listing existing servers (Glama indexes thousands). Some
+ add light hosting on top, but the core value is the catalog and the traffic. Use them to
+ publish a server people can find, or to find one that already does what you need.
+
+
+
2. Connector platforms
+
+ Composio, Nango, Klavis,{' '}
+ Zapier and Pipedream expose their catalog of
+ hundreds of pre-built SaaS integrations as MCP, with managed auth. If your need is
+ "let my agent touch Gmail / Slack / Salesforce," these are the fastest path —
+ you're buying breadth of pre-built connectors, not building your own logic.
+
+
+
3. Hosting infrastructure
+
+ Cloudflare Workers is the default for hosting a remote MCP server in
+ production — edge-global, with an OAuth provider library. Vercel,{' '}
+ Render and Cloud Run host custom Node containers too.{' '}
+ MintMCP sits slightly higher up: one-click wrap of an existing STDIO
+ server into a remote one with auto-OAuth, and it leads on compliance (SOC 2 Type II,
+ GDPR/HIPAA-formatted audit logs). All of these assume you bring the code.
+
+
+
4. Generators (the gap most lists miss)
+
+ The first three categories all assume you already have a server, or that a pre-built
+ connector covers your case. Neither is true when you need a bespoke tool — a
+ wrapper around your own internal API, a niche workflow, a one-off integration nobody has
+ built. That's the generator category: describe the tool, get a custom MCP server hosted
+ for you. It's the youngest and least crowded slice, and it's where{' '}
+ BuildMyMCPServer plays.
+
+
+
So which do you pick?
+
+
+ Need a popular SaaS connector? A connector platform (Composio / Klavis /
+ Zapier) — don't rebuild what they maintain.
+
+
+ Have a server and engineers? Host it on Cloudflare Workers; wrap your
+ own OAuth or use MintMCP if you want the compliance posture done for you.
+
+
+ Just browsing for something that exists? Smithery or Glama.
+
+
+ Need a custom tool and don't want to write or host a server? A generator
+ — describe it, ship it. Export the source later if you outgrow it.
+
+
+
+
Where BuildMyMCPServer fits honestly
+
+ We're not trying to out-scale Cloudflare's edge or out-catalog Composio. The job we do is
+ the bespoke one: prompt → a hosted, OAuth-protected MCP server, with
+ install snippets for Claude, Cursor and ChatGPT, an EU/US data-residency choice for teams
+ that care, full TypeScript source export, and a template marketplace to fork from. If your
+ tool is custom and your time is the constraint, that's the wedge. If you need a vetted
+ enterprise SOC 2 host for an existing server today, MintMCP or your own Cloudflare setup is
+ the more honest answer.
+
+
+
+ Next:{' '}
+
+ what hosting a remote MCP server with OAuth actually involves
+
+ .
+
+
+ >
+ );
+}
diff --git a/apps/web/app/(marketing)/guides/mintmcp-alternative/page.tsx b/apps/web/app/(marketing)/guides/mintmcp-alternative/page.tsx
new file mode 100644
index 0000000..7f2e7f7
--- /dev/null
+++ b/apps/web/app/(marketing)/guides/mintmcp-alternative/page.tsx
@@ -0,0 +1,96 @@
+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/mintmcp-alternative';
+const TITLE = 'MintMCP alternative: generate and host a custom MCP server';
+const DESCRIPTION =
+ 'MintMCP wraps an existing STDIO server into a remote one with OAuth. If you do not have a server yet, here is the generate-from-a-prompt alternative — and where MintMCP still wins.';
+
+export const metadata = pageMetadata({ title: TITLE, description: DESCRIPTION, path: PATH });
+
+export default function Page() {
+ return (
+ <>
+
+
+
What MintMCP does well
+
+ MintMCP takes a local STDIO-based MCP server you already wrote and turns
+ it into a production remote deployment — one-click, with automatic OAuth wrapping. Its
+ headline strength is compliance: SOC 2 Type II, with audit logs in SOC 2,
+ HIPAA and GDPR-friendly formats. For an enterprise that already has a server and needs the
+ certifications signed off, that's a strong, honest fit.
+
+
+
Where it leaves a gap
+
+ The model assumes the hard part — designing and writing the server — is already done. If
+ you're starting from "I need a tool that does X" and there's no code
+ yet, a wrapper doesn't help. You still have to learn the MCP SDK, write and test the tool
+ logic, then bring it over.
+
+
+
The alternative: start from the prompt
+
+ BuildMyMCPServer covers the step before the wrap. You describe the tool in
+ plain language; it generates the TypeScript MCP server, runs static checks against banned
+ patterns, builds a container, and deploys it behind a full OAuth 2.1 authorization server
+ (PKCE, Dynamic Client Registration, Resource Indicators). You get copy-paste install
+ snippets for Claude Desktop, Cursor and ChatGPT, and the full source to export whenever you
+ want.
+
+
+
Pick by your starting point
+
+
+ You have a working STDIO server + need SOC 2/HIPAA today → MintMCP is
+ the more honest fit. We don't claim those certifications.
+
+
+ You have an idea, not a server → generate it here, ship in minutes, and
+ export the TypeScript if you later move it onto your own infra.
+
+
+ You're an agency building one-off tools for clients repeatedly →
+ generation + a fork-able template marketplace removes the per-client boilerplate.
+
+
+ You're in the EU/DACH and care where prompts go → we expose the provider
+ and offer a data-residency choice rather than defaulting everything to one region.
+
+
+
+
What's the same either way
+
+ Both deliver a remote, OAuth-protected MCP server at a stable URL that real clients can
+ install — neither leaves you hand-rolling the auth handshake. The difference is purely{' '}
+ where you start: with code, or with a sentence.
+
+
+
+ More on the landscape:{' '}
+
+ hosted MCP platforms compared
+
+ .
+
+
+ >
+ );
+}
diff --git a/apps/web/app/(marketing)/guides/page.tsx b/apps/web/app/(marketing)/guides/page.tsx
new file mode 100644
index 0000000..42064b7
--- /dev/null
+++ b/apps/web/app/(marketing)/guides/page.tsx
@@ -0,0 +1,64 @@
+import { pageMetadata } from '@/lib/seo';
+import Link from 'next/link';
+
+export const metadata = pageMetadata({
+ title: 'MCP guides',
+ description:
+ 'Practical guides on hosting, securing and shipping Model Context Protocol (MCP) servers — OAuth 2.1, remote transport, platform comparisons.',
+ path: '/guides',
+});
+
+const GUIDES = [
+ {
+ slug: 'host-mcp-server-with-oauth',
+ title: 'How to host a remote MCP server with OAuth (2026)',
+ description:
+ 'Streamable HTTP, OAuth 2.1, PKCE and Resource Indicators — what it actually takes to put a remote MCP server in production, and the shortcuts.',
+ tag: 'Guide',
+ },
+ {
+ slug: 'hosted-mcp-platforms-compared',
+ title: 'Hosted MCP platforms compared: Cloudflare, Smithery, Composio & generating your own',
+ description:
+ 'The MCP hosting landscape splits into four categories. Which one fits depends on whether you have a server already, need a catalog, or need bespoke logic.',
+ tag: 'Comparison',
+ },
+ {
+ slug: 'mintmcp-alternative',
+ title: 'MintMCP alternative: generate and host a custom MCP server',
+ description:
+ 'MintMCP wraps an existing STDIO server into a remote one. If you do not have a server yet, here is the generate-from-a-prompt route — and where MintMCP still wins.',
+ tag: 'Alternative',
+ },
+];
+
+export default function GuidesIndex() {
+ return (
+
+
MCP guides
+
+ Hosting, auth and shipping for Model Context Protocol servers — written for people building
+ real tools, not demos.
+
+
+ {GUIDES.map((g) => (
+
+
+ {g.tag}
+
+
+ {g.title}
+
+
+ {g.description}
+
+
+ ))}
+
+
+ );
+}
diff --git a/apps/web/app/(marketing)/layout.tsx b/apps/web/app/(marketing)/layout.tsx
index 555a57e..4288b8a 100644
--- a/apps/web/app/(marketing)/layout.tsx
+++ b/apps/web/app/(marketing)/layout.tsx
@@ -24,6 +24,9 @@ export default function MarketingLayout({ children }: { children: React.ReactNod
Docs
+
+ Guides
+
Changelog
diff --git a/apps/web/app/sitemap.ts b/apps/web/app/sitemap.ts
index b8b327e..a1ead88 100644
--- a/apps/web/app/sitemap.ts
+++ b/apps/web/app/sitemap.ts
@@ -1,4 +1,5 @@
import { SITE_URL } from '@/lib/seo';
+import { fetchPublicTemplateSlugs } from '@/lib/templates-server';
import type { MetadataRoute } from 'next';
type Entry = {
@@ -11,6 +12,10 @@ const ROUTES: Entry[] = [
{ path: '/', priority: 1.0, changeFrequency: 'weekly' },
{ path: '/pricing', priority: 0.9, changeFrequency: 'weekly' },
{ path: '/templates', priority: 0.9, changeFrequency: 'daily' },
+ { path: '/guides', priority: 0.8, changeFrequency: 'weekly' },
+ { path: '/guides/host-mcp-server-with-oauth', priority: 0.8, changeFrequency: 'monthly' },
+ { path: '/guides/hosted-mcp-platforms-compared', priority: 0.8, changeFrequency: 'monthly' },
+ { path: '/guides/mintmcp-alternative', priority: 0.7, changeFrequency: 'monthly' },
{ path: '/docs', priority: 0.8, changeFrequency: 'weekly' },
{ path: '/docs/concepts', priority: 0.7, changeFrequency: 'monthly' },
{ path: '/docs/oauth', priority: 0.7, changeFrequency: 'monthly' },
@@ -25,12 +30,24 @@ const ROUTES: Entry[] = [
{ path: '/terms', priority: 0.3, changeFrequency: 'yearly' },
];
-export default function sitemap(): MetadataRoute.Sitemap {
+export default async function sitemap(): Promise {
const now = new Date();
- return ROUTES.map((r) => ({
+ const staticEntries: MetadataRoute.Sitemap = ROUTES.map((r) => ({
url: `${SITE_URL}${r.path}`,
lastModified: now,
changeFrequency: r.changeFrequency,
priority: r.priority,
}));
+
+ // Marketplace templates — each public template is its own indexable page.
+ // Best-effort: if the API is unreachable the static entries still ship.
+ const slugs = await fetchPublicTemplateSlugs();
+ const templateEntries: MetadataRoute.Sitemap = slugs.map((slug) => ({
+ url: `${SITE_URL}/templates/${slug}`,
+ lastModified: now,
+ changeFrequency: 'weekly',
+ priority: 0.6,
+ }));
+
+ return [...staticEntries, ...templateEntries];
}
diff --git a/apps/web/app/templates/[slug]/collapsible-code.tsx b/apps/web/app/templates/[slug]/collapsible-code.tsx
new file mode 100644
index 0000000..fb9ee0a
--- /dev/null
+++ b/apps/web/app/templates/[slug]/collapsible-code.tsx
@@ -0,0 +1,34 @@
+'use client';
+
+import { CodeBlock } from '@/components/code-block';
+import { ChevronDown, ChevronRight } from 'lucide-react';
+import { useState } from 'react';
+
+/**
+ * Client island: the "audit the generated code before you fork" toggle. Kept
+ * out of the server-rendered page so the page itself stays static + indexable.
+ */
+export function CollapsibleCode({ code }: { code: string }) {
+ const [show, setShow] = useState(false);
+ return (
+
+
+
+ Audit before you fork. We re-scan every published template for banned patterns (eval,
+ child_process, prompt-injection markers).
+
+ {show && (
+
+
+
+ )}
+
+ );
+}
diff --git a/apps/web/app/templates/[slug]/page.tsx b/apps/web/app/templates/[slug]/page.tsx
index 23cad14..30b5627 100644
--- a/apps/web/app/templates/[slug]/page.tsx
+++ b/apps/web/app/templates/[slug]/page.tsx
@@ -1,95 +1,71 @@
-'use client';
-
-import { useEffect, useState } from 'react';
-import Link from 'next/link';
-import { useParams, useRouter } from 'next/navigation';
-import { ShieldCheck, GitFork, Activity, ExternalLink, ChevronDown, ChevronRight } from 'lucide-react';
-import { apiFetch } from '@/lib/api';
-import { Logo } from '@/components/logo';
-import { Button } from '@/components/ui/button';
import { CodeBlock } from '@/components/code-block';
+import { JsonLd } from '@/components/json-ld';
+import { Logo } from '@/components/logo';
+import { type TemplateDetail, fetchTemplate } from '@/lib/templates-server';
+import { pageMetadata, templateJsonLd } from '@/lib/seo';
+import type { Metadata } from 'next';
+import Link from 'next/link';
+import { notFound } from 'next/navigation';
+import { Activity, ExternalLink, GitFork, ShieldCheck } from 'lucide-react';
+import { CollapsibleCode } from './collapsible-code';
-interface Tool {
- name: string;
- description: string;
- inputSchema: Record;
+// Server-rendered for SEO: per-template , description, OpenGraph and
+// SoftwareApplication JSON-LD. The only interactive piece (the code-audit
+// toggle) lives in a client island.
+
+interface PageProps {
+ params: Promise<{ slug: string }>;
}
-interface SecretHint {
- key: string;
- description: string;
- howToGetUrl?: string;
+export async function generateMetadata({ params }: PageProps): Promise {
+ const { slug } = await params;
+ const t = await fetchTemplate(slug);
+ if (!t) {
+ return pageMetadata({
+ title: 'Template not found',
+ description: 'This MCP server template is not available.',
+ path: `/templates/${slug}`,
+ });
+ }
+ return pageMetadata({
+ title: `${t.title} — MCP server for Claude, Cursor & ChatGPT`,
+ description:
+ t.shortDescription.length > 0
+ ? t.shortDescription
+ : `Fork the ${t.title} MCP server and deploy your own OAuth-protected copy in seconds.`,
+ path: `/templates/${slug}`,
+ });
}
-interface TemplateDetail {
- id: string;
- slug: string;
- title: string;
- shortDescription: string;
- longDescription: string | null;
- category: string;
- status: 'draft' | 'public' | 'hidden' | 'takedown';
- verified: boolean;
- forkCount: number;
- activeDeployments: number;
- toolsSchema: Tool[];
- generatedCode: string;
- requiredSecrets: SecretHint[];
- scopes: string[];
- ownerName: string | null;
- ownerOrgName: string | null;
- sourceServerId: string | null;
- createdAt: string;
-}
+export default async function TemplateDetailPage({ params }: PageProps) {
+ const { slug } = await params;
+ const template: TemplateDetail | null = await fetchTemplate(slug);
+ if (!template) notFound();
-export default function TemplateDetail() {
- const params = useParams<{ slug: string }>();
- const router = useRouter();
- const [template, setTemplate] = useState(null);
- const [error, setError] = useState(null);
- const [showCode, setShowCode] = useState(false);
-
- useEffect(() => {
- apiFetch<{ template: TemplateDetail }>(`/v1/templates/${params.slug}`)
- .then((r) => setTemplate(r.template))
- .catch((e) => {
- const detail = (e as { detail?: { error?: string } }).detail;
- setError(detail?.error ?? (e as Error).message);
- });
- }, [params.slug]);
-
- function useTemplate() {
- if (!template) return;
- router.push(`/servers/new?template=${template.slug}`);
- }
-
- if (error) {
- return (
-