buildmymcpserver/apps/web/lib/seo.ts

211 lines
7.3 KiB
TypeScript
Raw Normal View History

// Central SEO source of truth — site constants, FAQ data, JSON-LD builders.
// The FAQ array here is rendered on the landing page AND emitted as FAQPage
// structured data, so the two never drift (Google requires them to match).
import type { Metadata } from 'next';
export const SITE_URL = process.env.NEXT_PUBLIC_APP_URL ?? 'https://buildmymcpserver.com';
export const SITE_NAME = 'BuildMyMCPServer';
export const SITE_TAGLINE = 'Describe your tool. We host the MCP server.';
export const SITE_DESCRIPTION =
'From a natural-language prompt to a hosted, OAuth 2.1-protected MCP server in 60 seconds. Streamable HTTP, ready for Claude, Cursor and ChatGPT.';
export const SEO_KEYWORDS = [
'MCP server',
'Model Context Protocol',
'hosted MCP server',
'MCP server hosting',
'MCP server builder',
'create MCP server',
'deploy MCP server',
'MCP server generator',
'OAuth MCP server',
'Streamable HTTP MCP',
'Claude MCP server',
'Cursor MCP',
'ChatGPT connector',
'MCP template marketplace',
];
export interface FaqItem {
q: string;
a: string;
}
// Rendered on the landing page and emitted as FAQPage JSON-LD — keep in sync
// by virtue of being a single export.
export const FAQ: FaqItem[] = [
{
q: 'What is MCP?',
a: 'Model Context Protocol — an open standard from Anthropic for connecting AI assistants to external tools, data and APIs over a transport like Streamable HTTP.',
},
{
q: 'Do I need to write code?',
a: 'No. You describe the tool in natural language. We generate the TypeScript server, run static checks, build a Docker image and deploy it to a public OAuth-protected URL.',
},
{
q: 'Which clients work?',
a: 'Claude Desktop, Cursor, ChatGPT Custom Connectors, VS Code Copilot, Continue.dev — anything that speaks the MCP spec.',
},
{
q: 'How is auth handled?',
a: 'Every generated server is an OAuth 2.1 Resource Server. Our control plane is the Authorization Server (PKCE + Dynamic Client Registration + Resource Indicators per RFC 8707).',
},
{
q: 'Can I self-host?',
a: 'Yes. The runner is a plain Docker container; the control plane is open to BYO Postgres + Redis. See the self-hosting guide in docs.',
},
{
q: 'What about secrets?',
a: 'AES-256-GCM at rest in Postgres, injected as environment variables into the runtime container. Never logged, never echoed back.',
},
{
q: 'Cold starts?',
a: 'No cold starts. Containers stay warm. Sub-50ms tool-call overhead on average for in-region requests.',
},
{
q: 'Rate limits?',
a: 'Default 100 requests/min/IP per tool. Configurable per server. Quota enforced at the proxy layer before hitting your container.',
},
{
q: 'How fast is generation?',
a: 'Spec to image to live URL typically completes in 45-90 seconds.',
},
{
q: 'Logs and metrics?',
a: 'Live log streaming to the dashboard, structured tool-call metrics (P50/P95/P99 latency, error rate, per-tool throughput) — all retained for 30 days.',
},
{
q: 'What if I cancel?',
a: 'You can export the full TypeScript source of every server you built. No vendor lock-in.',
},
{
q: 'Custom domain?',
a: 'Pro plan and above. Add a CNAME, we provision a TLS certificate automatically.',
},
];
const SOFTWARE_FEATURES = [
'Prompt to a deployed MCP server in under 60 seconds',
'OAuth 2.1 authorization server — PKCE, Dynamic Client Registration (RFC 7591), Resource Indicators (RFC 8707)',
'Streamable HTTP transport, compatible with Claude Desktop, Cursor, ChatGPT, VS Code Copilot and Continue.dev',
'Every generated server runs in an isolated Docker container',
'Customer secrets encrypted with AES-256-GCM, injected only at runtime',
'Live build-log streaming to the dashboard',
'Template marketplace — publish your server or fork someone elses',
'Full TypeScript source export, no vendor lock-in',
'Self-hostable control plane with BYO Postgres and Redis',
];
const OFFERS = [
{
name: 'Hobby',
price: '0',
description: '1 server, 100k tool calls/month, BMM subdomain, community support.',
},
{
name: 'Pro',
price: '49',
description:
'5 servers, 1M tool calls/month, custom domain, priority build queue, email support.',
},
{
name: 'Team',
price: '149',
description: '25 servers, 10M tool calls/month, RBAC + audit log, 99.9% SLA, Slack support.',
},
{
name: 'Enterprise',
price: '499',
description:
'Unlimited servers, bring-your-own-cloud, SSO/SAML, dedicated cluster, customer success.',
},
];
/** Organization + WebSite + SoftwareApplication graph — emitted sitewide. */
export function siteJsonLd(): object {
return {
'@context': 'https://schema.org',
'@graph': [
{
'@type': 'Organization',
'@id': `${SITE_URL}/#organization`,
name: SITE_NAME,
url: SITE_URL,
logo: { '@type': 'ImageObject', url: `${SITE_URL}/icon.svg` },
foundingDate: '2026',
foundingLocation: {
'@type': 'Place',
address: { '@type': 'PostalAddress', addressCountry: 'CH' },
},
},
{
'@type': 'WebSite',
'@id': `${SITE_URL}/#website`,
url: SITE_URL,
name: SITE_NAME,
publisher: { '@id': `${SITE_URL}/#organization` },
description: SITE_DESCRIPTION,
inLanguage: 'en',
},
{
'@type': 'SoftwareApplication',
'@id': `${SITE_URL}/#software`,
name: SITE_NAME,
applicationCategory: 'DeveloperApplication',
operatingSystem: 'Web Browser',
url: SITE_URL,
inLanguage: 'en',
description:
'BuildMyMCPServer turns a natural-language prompt into a hosted, OAuth 2.1-protected Model Context Protocol (MCP) server. Describe the tools you need; the platform generates a TypeScript server, runs static checks, builds a Docker image and deploys it to a public Streamable HTTP endpoint that Claude, Cursor and ChatGPT can connect to. A template marketplace lets users publish and fork ready-made servers.',
featureList: SOFTWARE_FEATURES,
offers: OFFERS.map((o) => ({
'@type': 'Offer',
name: o.name,
price: o.price,
priceCurrency: 'EUR',
description: o.description,
})),
},
],
};
}
/** FAQPage structured data — emitted on the page that displays the FAQ. */
export function faqJsonLd(items: FaqItem[] = FAQ): object {
return {
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: items.map((item) => ({
'@type': 'Question',
name: item.q,
acceptedAnswer: { '@type': 'Answer', text: item.a },
})),
};
}
/**
* Per-page metadata. `title` is a bare string so the root layout's
* "%s | BuildMyMCPServer" template appends the brand exactly once.
*/
export function pageMetadata(opts: { title: string; description: string; path: string }): Metadata {
const fullTitle = `${opts.title} | ${SITE_NAME}`;
return {
title: opts.title,
description: opts.description,
alternates: { canonical: opts.path },
openGraph: {
type: 'website',
siteName: SITE_NAME,
title: fullTitle,
description: opts.description,
url: `${SITE_URL}${opts.path}`,
},
twitter: {
card: 'summary_large_image',
title: fullTitle,
description: opts.description,
},
};
}