feat(content): 11 new SEO/GEO guide articles with per-article OG images
How-to cluster: create-mcp-server-without-code, claude-desktop-mcp-setup, chatgpt-mcp-connector (incl. honest Business/Enterprise write-connector plan limits), rest-api-to-mcp-server. Comparison cluster: mcp-server-hosting-pricing (5-platform matrix), composio-alternative, smithery-alternative — house style: explicitly state where competitors win. Technical/GEO cluster: mcp-transports-explained (SSE deprecation 2025-03-26), mcp-oauth-plain-english, mcp-server-security-checklist, and German DACH article mcp-server-ohne-code-erstellen (articleJsonLd gained optional inLanguage param). Every article: pageMetadata canonical, Article JSON-LD with Person author + image + wordCount, BreadcrumbList, FAQPage where applicable, 1200x630 OG image via shared helper, internal linking. Registry entries in lib/articles.ts feed guides index, sitemap and RSS automatically. Product claims sourced only from lib/seo.ts truth — no invented customers, SLAs or certifications. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SXUwmPVRTD8AKQtio6gCN5
This commit is contained in:
parent
089074d104
commit
a08f5f05b1
@ -0,0 +1,13 @@
|
||||
import { articleOgImage, OG_SIZE } from '@/lib/og-article';
|
||||
|
||||
export const runtime = 'edge';
|
||||
export const alt = 'Add a custom MCP connector to ChatGPT (2026 guide)';
|
||||
export const size = OG_SIZE;
|
||||
export const contentType = 'image/png';
|
||||
|
||||
export default function Image() {
|
||||
return articleOgImage({
|
||||
title: 'Add a custom MCP connector to ChatGPT (2026 guide)',
|
||||
tag: 'Setup',
|
||||
});
|
||||
}
|
||||
200
apps/web/app/(marketing)/guides/chatgpt-mcp-connector/page.tsx
Normal file
200
apps/web/app/(marketing)/guides/chatgpt-mcp-connector/page.tsx
Normal file
@ -0,0 +1,200 @@
|
||||
import { JsonLd } from '@/components/json-ld';
|
||||
import { articleJsonLd, breadcrumbJsonLd, pageMetadata } from '@/lib/seo';
|
||||
import Link from 'next/link';
|
||||
import { ArticleShell, H2, Note, OL, P, Strong, Table, UL } from '../article-shell';
|
||||
|
||||
const PATH = '/guides/chatgpt-mcp-connector';
|
||||
const TITLE = 'Add a custom MCP connector to ChatGPT (2026 guide)';
|
||||
const DESCRIPTION =
|
||||
'How to connect a custom MCP server to ChatGPT: setup flow, the HTTPS and OAuth requirements, and the plan limits nobody mentions — write-capable connectors need a Business, Enterprise or Edu workspace.';
|
||||
|
||||
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-07-08',
|
||||
authorName: 'Marco Sadjadi',
|
||||
wordCount: 1300,
|
||||
})}
|
||||
/>
|
||||
<JsonLd
|
||||
data={breadcrumbJsonLd([
|
||||
{ name: 'Home', path: '/' },
|
||||
{ name: 'Guides', path: '/guides' },
|
||||
{ name: TITLE, path: PATH },
|
||||
])}
|
||||
/>
|
||||
<ArticleShell
|
||||
title={TITLE}
|
||||
subtitle="ChatGPT has supported custom MCP connectors since September 2025 — but what you can actually do with one depends on your plan, and the requirements for the server side are stricter than Claude's. Here is the full picture before you build."
|
||||
updated="July 2026"
|
||||
>
|
||||
<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:
|
||||
</P>
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Plan</th>
|
||||
<th>Custom MCP connectors</th>
|
||||
<th>Practical meaning</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Free</td>
|
||||
<td>No custom connectors</td>
|
||||
<td>Only built-in connectors.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Plus / Pro (individual)</td>
|
||||
<td>Read/fetch-only, via Developer Mode</td>
|
||||
<td>
|
||||
Your server's search and read tools work; tools that create, update or delete will
|
||||
not be usable.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Business / Enterprise / Edu</td>
|
||||
<td>Full connectors, including write-capable tools</td>
|
||||
<td>The complete MCP tool surface is available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
<Note>
|
||||
This is the most common “my connector is broken” report that is not a bug: a Plus user
|
||||
adds a server with a <Strong>create_issue</Strong> tool and the tool never fires.
|
||||
Read-only tools on the same server work fine. If write actions matter for your use case,
|
||||
you need a Business/Enterprise/Edu workspace — or a client without this restriction, like
|
||||
Claude Desktop (
|
||||
<Link href="/guides/claude-desktop-mcp-setup" className="text-[--color-accent] hover:underline">
|
||||
setup guide
|
||||
</Link>
|
||||
).
|
||||
</Note>
|
||||
|
||||
<H2>What ChatGPT requires from the server</H2>
|
||||
<UL>
|
||||
<li>
|
||||
<Strong>A public HTTPS URL.</Strong> Remote MCP server URLs must use HTTPS — no local
|
||||
STDIO servers, no plain HTTP, no localhost tunnels for anything durable.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Streamable HTTP transport.</Strong> The current MCP remote transport; legacy
|
||||
HTTP+SSE-only servers are on borrowed time across all clients.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>OAuth for user-scoped auth.</Strong> ChatGPT walks the standard MCP OAuth flow.
|
||||
One sharp edge: if the authorization server issues tokens without{' '}
|
||||
<Strong>offline_access</Strong>-style refresh, ChatGPT can lose access when the token
|
||||
expires and users must reauthenticate.
|
||||
</li>
|
||||
</UL>
|
||||
<P>
|
||||
If you generate and host your server on{' '}
|
||||
<Link href="/" className="text-[--color-accent] hover:underline">
|
||||
BuildMyMCPServer
|
||||
</Link>
|
||||
, all three are the default: every server deploys to a public HTTPS endpoint speaking
|
||||
Streamable HTTP, behind an OAuth 2.1 authorization server with PKCE and Dynamic Client
|
||||
Registration. There is nothing extra to configure for ChatGPT specifically.
|
||||
</P>
|
||||
|
||||
<H2>Setup, step by step</H2>
|
||||
<OL>
|
||||
<li>
|
||||
Get your server URL — from your own deployment, from{' '}
|
||||
<Link href="/guides/create-mcp-server-without-code" className="text-[--color-accent] hover:underline">
|
||||
a prompt-generated server
|
||||
</Link>
|
||||
, or by forking a{' '}
|
||||
<Link href="/templates" className="text-[--color-accent] hover:underline">
|
||||
template
|
||||
</Link>
|
||||
.
|
||||
</li>
|
||||
<li>
|
||||
In ChatGPT: <Strong>Settings → Apps & Connectors</Strong>. On individual plans,
|
||||
enable <Strong>Developer Mode</Strong> under advanced settings first — the “create
|
||||
connector” option is hidden without it.
|
||||
</li>
|
||||
<li>Add a new connector: name it, paste the MCP endpoint URL, select OAuth as the auth method.</li>
|
||||
<li>
|
||||
ChatGPT registers itself with the authorization server and opens the consent screen.
|
||||
Approve; the connector shows as connected.
|
||||
</li>
|
||||
<li>
|
||||
In a conversation, enable the connector (via the tools/plus menu) and ask for something
|
||||
only your tool can answer. Name the tool explicitly on the first test.
|
||||
</li>
|
||||
</OL>
|
||||
|
||||
<H2>Verifying it actually works</H2>
|
||||
<P>
|
||||
ChatGPT is more eager than Claude to answer from its own knowledge instead of calling a
|
||||
tool. To force a real call, ask for data the model cannot know — a record you created
|
||||
today, a value behind your API. Then check the server's dashboard: a live tool-call log
|
||||
with latency and status per call is the ground truth for whether the connector fired.
|
||||
</P>
|
||||
|
||||
<H2>Common failure modes</H2>
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Symptom</th>
|
||||
<th>Cause</th>
|
||||
<th>Fix</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>“Create connector” option missing</td>
|
||||
<td>Developer Mode off, or Free plan</td>
|
||||
<td>Enable Developer Mode (Plus/Pro) or upgrade the workspace.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Write tools never execute</td>
|
||||
<td>Individual plan — read/fetch-only restriction</td>
|
||||
<td>Business/Enterprise/Edu workspace, or use a write-capable client.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Connector disconnects after hours/days</td>
|
||||
<td>No refresh token (offline_access missing)</td>
|
||||
<td>Reconnect; if you control the AS, enable refresh token issuance.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>“Unable to reach server”</td>
|
||||
<td>URL is not public HTTPS, or wrong endpoint path</td>
|
||||
<td>Use the full https://…/mcp endpoint; no localhost, no http.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
|
||||
<H2>Is ChatGPT the right first client?</H2>
|
||||
<P>
|
||||
If your tools are read-only — search, lookup, reporting — ChatGPT works well on any paid
|
||||
plan and the setup above takes minutes. If your tools write data, start with Claude
|
||||
Desktop or Cursor where the full tool surface works on individual plans, and add ChatGPT
|
||||
when a team workspace exists. The server is the same either way; only the client config
|
||||
differs. Compare hosting options in{' '}
|
||||
<Link href="/guides/hosted-mcp-platforms-compared" className="text-[--color-accent] hover:underline">
|
||||
our platform comparison
|
||||
</Link>
|
||||
, or check{' '}
|
||||
<Link href="/pricing" className="text-[--color-accent] hover:underline">
|
||||
pricing
|
||||
</Link>{' '}
|
||||
— the free tier is enough to test a connector end to end.
|
||||
</P>
|
||||
</ArticleShell>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
import { articleOgImage, OG_SIZE } from '@/lib/og-article';
|
||||
|
||||
export const runtime = 'edge';
|
||||
export const alt = 'Connect a custom MCP server to Claude Desktop (step by step)';
|
||||
export const size = OG_SIZE;
|
||||
export const contentType = 'image/png';
|
||||
|
||||
export default function Image() {
|
||||
return articleOgImage({
|
||||
title: 'Connect a custom MCP server to Claude Desktop (step by step)',
|
||||
tag: 'Setup',
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,189 @@
|
||||
import { JsonLd } from '@/components/json-ld';
|
||||
import { StaticCodeBlock } from '@/components/static-code-block';
|
||||
import { articleJsonLd, breadcrumbJsonLd, pageMetadata } from '@/lib/seo';
|
||||
import Link from 'next/link';
|
||||
import { ArticleShell, H2, Note, OL, P, Strong, Table, UL } from '../article-shell';
|
||||
|
||||
const PATH = '/guides/claude-desktop-mcp-setup';
|
||||
const TITLE = 'Connect a custom MCP server to Claude Desktop (step by step)';
|
||||
const DESCRIPTION =
|
||||
'How to add a remote MCP server to Claude Desktop: the config snippet, the OAuth flow on first use, and a troubleshooting table for 401s, missing servers and invisible tools.';
|
||||
|
||||
export const metadata = pageMetadata({ title: TITLE, description: DESCRIPTION, path: PATH });
|
||||
|
||||
const CONFIG_SNIPPET = `{
|
||||
"mcpServers": {
|
||||
"my-tools": {
|
||||
"url": "https://my-tools-a1.mcp.buildmymcpserver.com/mcp",
|
||||
"auth": "oauth2"
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
const LOCAL_VS_REMOTE = `# Local (STDIO) — runs on your machine, per-machine setup
|
||||
"command": "npx", "args": ["-y", "@your/mcp-server"]
|
||||
|
||||
# Remote (Streamable HTTP) — hosted, one URL for every machine
|
||||
"url": "https://my-tools-a1.mcp.buildmymcpserver.com/mcp"`;
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<JsonLd
|
||||
data={articleJsonLd({
|
||||
title: TITLE,
|
||||
description: DESCRIPTION,
|
||||
path: PATH,
|
||||
datePublished: '2026-07-08',
|
||||
authorName: 'Marco Sadjadi',
|
||||
wordCount: 1400,
|
||||
})}
|
||||
/>
|
||||
<JsonLd
|
||||
data={breadcrumbJsonLd([
|
||||
{ name: 'Home', path: '/' },
|
||||
{ name: 'Guides', path: '/guides' },
|
||||
{ name: TITLE, path: PATH },
|
||||
])}
|
||||
/>
|
||||
<ArticleShell
|
||||
title={TITLE}
|
||||
subtitle="Claude Desktop supports both local STDIO servers and remote servers over Streamable HTTP. Remote is the version that survives a laptop change — here is the exact setup, including what the OAuth consent screen is doing."
|
||||
updated="July 2026"
|
||||
>
|
||||
<H2>Local vs. remote — pick remote unless you have a reason</H2>
|
||||
<P>
|
||||
Most MCP tutorials wire up a <Strong>local STDIO server</Strong>: Claude Desktop spawns a
|
||||
process on your machine and talks to it over stdin/stdout. That works, but it is
|
||||
per-machine — every teammate repeats the setup, secrets live in local config files, and
|
||||
nothing works from a second device. A <Strong>remote server over Streamable HTTP</Strong>{' '}
|
||||
is one URL that every installation shares, with auth handled by OAuth instead of
|
||||
plaintext keys in a JSON file.
|
||||
</P>
|
||||
<StaticCodeBlock code={LOCAL_VS_REMOTE} label="the difference in config terms" />
|
||||
|
||||
<H2>Step 1 — get a server URL</H2>
|
||||
<P>
|
||||
You need a live MCP endpoint. If you already run one, use its URL. If not, you can{' '}
|
||||
<Link href="/guides/create-mcp-server-without-code" className="text-[--color-accent] hover:underline">
|
||||
generate one from a prompt
|
||||
</Link>{' '}
|
||||
or fork a working one from{' '}
|
||||
<Link href="/templates" className="text-[--color-accent] hover:underline">
|
||||
the template gallery
|
||||
</Link>{' '}
|
||||
— either way you end up with an OAuth-protected URL like{' '}
|
||||
<Strong>https://my-tools-a1.mcp.buildmymcpserver.com/mcp</Strong>.
|
||||
</P>
|
||||
|
||||
<H2>Step 2 — add the server to Claude Desktop</H2>
|
||||
<OL>
|
||||
<li>
|
||||
Open Claude Desktop settings and go to the connectors/MCP section (on recent versions:
|
||||
Settings → Connectors → Add custom connector), or edit{' '}
|
||||
<Strong>claude_desktop_config.json</Strong> directly.
|
||||
</li>
|
||||
<li>Add the server entry:</li>
|
||||
</OL>
|
||||
<StaticCodeBlock code={CONFIG_SNIPPET} label="claude_desktop_config.json" />
|
||||
<OL>
|
||||
<li value={3}>Restart Claude Desktop. Config is read at startup, not live.</li>
|
||||
</OL>
|
||||
<Note>
|
||||
Claude Desktop's settings UI changes between releases; the JSON config path is the stable
|
||||
fallback. On macOS it lives at{' '}
|
||||
<Strong>~/Library/Application Support/Claude/claude_desktop_config.json</Strong>, on
|
||||
Windows at <Strong>%APPDATA%\Claude\claude_desktop_config.json</Strong>.
|
||||
</Note>
|
||||
|
||||
<H2>Step 3 — the OAuth flow on first use</H2>
|
||||
<P>
|
||||
The first time Claude touches the server it will receive a <Strong>401</Strong> — that is
|
||||
correct behavior, not an error. The client then discovers the authorization server,
|
||||
registers itself via Dynamic Client Registration, and opens a browser window for consent.
|
||||
You approve once; the client stores the token and refreshes it silently afterwards.
|
||||
</P>
|
||||
<P>
|
||||
Under the hood this is the OAuth 2.1 handshake the MCP spec requires for remote servers:
|
||||
PKCE so the code exchange cannot be intercepted, and Resource Indicators (RFC 8707) so a
|
||||
token issued for this server cannot be replayed against another. Details in{' '}
|
||||
<Link href="/docs/oauth" className="text-[--color-accent] hover:underline">
|
||||
the OAuth documentation
|
||||
</Link>
|
||||
.
|
||||
</P>
|
||||
|
||||
<H2>Step 4 — verify the tools are there</H2>
|
||||
<UL>
|
||||
<li>Open a new conversation and check the tools/connectors icon — your server should be listed with its tools.</li>
|
||||
<li>
|
||||
Ask Claude directly: <em>“Use the search_pages tool to find X.”</em> Naming the tool
|
||||
forces Claude to attempt the call instead of answering from memory.
|
||||
</li>
|
||||
<li>Watch your server's dashboard logs — you should see the tool call arrive with latency and status.</li>
|
||||
</UL>
|
||||
|
||||
<H2>Troubleshooting</H2>
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Symptom</th>
|
||||
<th>Likely cause</th>
|
||||
<th>Fix</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Server never appears in the client</td>
|
||||
<td>Config JSON is invalid, or Claude was not restarted</td>
|
||||
<td>
|
||||
Validate the JSON (trailing commas are the classic), restart Claude Desktop fully
|
||||
(quit, not close window).
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Endless 401 loop, consent screen never opens</td>
|
||||
<td>URL points at the server root instead of the /mcp endpoint</td>
|
||||
<td>Use the full endpoint URL ending in /mcp, exactly as the install snippet shows.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Consent worked, tools still missing</td>
|
||||
<td>Server is deployed but tool listing failed on connect</td>
|
||||
<td>
|
||||
Check the server's live logs for an initialize/list_tools error; redeploy if the
|
||||
container restarted with a bad secret.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Tool listed, every call errors</td>
|
||||
<td>Upstream credential (e.g. your API key) is wrong or expired</td>
|
||||
<td>
|
||||
Update the secret in the dashboard — secrets are injected at runtime, so a redeploy
|
||||
picks up the new value. They are never shown back, so re-enter rather than inspect.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Worked yesterday, 401 today</td>
|
||||
<td>Token expired and silent refresh failed</td>
|
||||
<td>Remove and re-add the connector to force a fresh OAuth flow.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
|
||||
<H2>One server, every client</H2>
|
||||
<P>
|
||||
The same URL works in Cursor, VS Code Copilot, Continue.dev and{' '}
|
||||
<Link href="/guides/chatgpt-mcp-connector" className="text-[--color-accent] hover:underline">
|
||||
ChatGPT custom connectors
|
||||
</Link>{' '}
|
||||
— the point of hosting a remote MCP server is that the client config is the only
|
||||
per-client step left. If you hit a case this guide does not cover, the{' '}
|
||||
<Link href="/docs/faq" className="text-[--color-accent] hover:underline">
|
||||
docs FAQ
|
||||
</Link>{' '}
|
||||
collects the rarer ones.
|
||||
</P>
|
||||
</ArticleShell>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
import { articleOgImage, OG_SIZE } from '@/lib/og-article';
|
||||
|
||||
export const runtime = 'edge';
|
||||
export const alt = 'Composio alternative for bespoke MCP tools';
|
||||
export const size = OG_SIZE;
|
||||
export const contentType = 'image/png';
|
||||
|
||||
export default function Image() {
|
||||
return articleOgImage({
|
||||
title: 'Composio alternative for bespoke MCP tools',
|
||||
tag: 'Alternative',
|
||||
});
|
||||
}
|
||||
206
apps/web/app/(marketing)/guides/composio-alternative/page.tsx
Normal file
206
apps/web/app/(marketing)/guides/composio-alternative/page.tsx
Normal file
@ -0,0 +1,206 @@
|
||||
import { JsonLd } from '@/components/json-ld';
|
||||
import { articleJsonLd, breadcrumbJsonLd, pageMetadata } from '@/lib/seo';
|
||||
import Link from 'next/link';
|
||||
import { ArticleShell, H2, Note, P, Strong, Table, UL } from '../article-shell';
|
||||
|
||||
const PATH = '/guides/composio-alternative';
|
||||
const TITLE = 'Composio alternative for bespoke MCP tools';
|
||||
const DESCRIPTION =
|
||||
'Composio gives agents 1,000+ pre-built SaaS integrations. The gap is everything not in a catalog: your internal API, your database, your workflow. Here is the generate-your-own route — and where Composio clearly wins.';
|
||||
|
||||
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-07-08',
|
||||
authorName: 'Marco Sadjadi',
|
||||
wordCount: 1200,
|
||||
})}
|
||||
/>
|
||||
<JsonLd
|
||||
data={breadcrumbJsonLd([
|
||||
{ name: 'Home', path: '/' },
|
||||
{ name: 'Guides', path: '/guides' },
|
||||
{ name: TITLE, path: PATH },
|
||||
])}
|
||||
/>
|
||||
<ArticleShell
|
||||
title={TITLE}
|
||||
subtitle="Composio and BuildMyMCPServer both put tools in front of your AI agent — but they answer opposite questions. Composio: 'which of these 1,000 integrations do you want?' Us: 'describe the one that doesn't exist yet.'"
|
||||
updated="July 2026"
|
||||
>
|
||||
<H2>What Composio does well</H2>
|
||||
<P>
|
||||
Composio's pitch is breadth with managed auth: as of mid-2026 it exposes{' '}
|
||||
<Strong>1,000+ third-party applications</Strong> — Gmail, Slack, Notion, Salesforce,
|
||||
HubSpot, GitHub, Linear, Stripe, Shopify and the rest of the mainstream SaaS universe — as
|
||||
agent-callable tools behind one MCP gateway, with the OAuth dance to each SaaS handled for
|
||||
you. Pricing is per tool call: a free tier around 20k calls/month, then paid tiers from
|
||||
$29/month. If the integration you need is in that catalog, rebuilding it yourself is
|
||||
almost always the wrong use of your time. That is an honest, strong product.
|
||||
</P>
|
||||
|
||||
<H2>Where the catalog model hits its ceiling</H2>
|
||||
<P>
|
||||
A catalog, by definition, contains what many companies share. It cannot contain what only
|
||||
your company has:
|
||||
</P>
|
||||
<UL>
|
||||
<li>
|
||||
<Strong>Internal APIs</Strong> — the ERP endpoint, the pricing service, the legacy SOAP
|
||||
bridge your team wrapped in REST five years ago.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Your database</Strong> — a read-only reporting view with exactly the columns
|
||||
the agent may see, and none it may not.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Custom logic between systems</Strong> — "check inventory, then draft the
|
||||
reorder in our format" is a tool, not two catalog entries.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Data-control requirements</Strong> — some teams cannot route production
|
||||
traffic and credentials through a third-party tool-execution layer at all.
|
||||
</li>
|
||||
</UL>
|
||||
<P>
|
||||
When you hit that ceiling with a catalog product, the fallback is suddenly steep: learn
|
||||
the MCP SDK, write and test a server, stand up hosting and OAuth. That cliff is the gap.
|
||||
</P>
|
||||
|
||||
<H2>The alternative: generate the bespoke tool</H2>
|
||||
<P>
|
||||
<Strong>BuildMyMCPServer</Strong> starts where the catalog ends. You describe the tool in
|
||||
natural language — endpoints, secrets, behavior. The platform generates a TypeScript MCP
|
||||
server, runs static checks, builds an isolated container and deploys it behind a full
|
||||
OAuth 2.1 authorization server (PKCE, Dynamic Client Registration, Resource Indicators),
|
||||
with copy-paste install snippets for Claude Desktop, Cursor and ChatGPT. Secrets are
|
||||
AES-256-GCM encrypted and injected only at runtime. You can export the full TypeScript
|
||||
source at any time — the generated server is yours, not a subscription artifact.
|
||||
</P>
|
||||
|
||||
<H2>Side by side</H2>
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>Composio</th>
|
||||
<th>BuildMyMCPServer</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<Strong>Core object</Strong>
|
||||
</td>
|
||||
<td>Their catalog of pre-built integrations</td>
|
||||
<td>A custom server generated from your prompt</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Strong>Best for</Strong>
|
||||
</td>
|
||||
<td>Mainstream SaaS (Gmail, Slack, Salesforce…)</td>
|
||||
<td>Internal APIs, databases, bespoke workflows</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Strong>Auth to end services</Strong>
|
||||
</td>
|
||||
<td>Managed OAuth to hundreds of SaaS — a real moat</td>
|
||||
<td>You supply credentials for your own APIs</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Strong>Billing shape</Strong>
|
||||
</td>
|
||||
<td>Per tool call (free 20k/mo, then usage tiers)</td>
|
||||
<td>Per server tier with call allowance (free: 1 server, 100k calls/mo)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Strong>Code ownership</Strong>
|
||||
</td>
|
||||
<td>No server code of yours exists</td>
|
||||
<td>Full TypeScript source export, no lock-in</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Strong>Maturity</Strong>
|
||||
</td>
|
||||
<td>Established, large developer base</td>
|
||||
<td>Young product (launched 2026), EU-hosted</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
|
||||
<Note>
|
||||
Composio details reflect its public pricing and catalog as of mid-2026; verify current
|
||||
numbers on their site before committing budget.
|
||||
</Note>
|
||||
|
||||
<H2>Pick by the question you are actually asking</H2>
|
||||
<UL>
|
||||
<li>
|
||||
<Strong>"Let my agent use Slack and Gmail"</Strong> → Composio. Do not
|
||||
generate what a maintained catalog already does better.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>"Let my agent use our internal order API, read-only"</Strong> → a
|
||||
generated bespoke server. No catalog will ever have it.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Both?</Strong> → they compose. MCP clients speak to multiple servers; a catalog
|
||||
gateway for SaaS plus one generated server for the internal surface is a normal setup,
|
||||
not a compromise.
|
||||
</li>
|
||||
</UL>
|
||||
|
||||
<H2>Honest limits of the generator route</H2>
|
||||
<P>
|
||||
Generation is bounded by what a prompt can specify: tools with clear inputs, outputs and
|
||||
API calls. A deeply stateful integration with complex pagination and exotic auth may
|
||||
still be a hand-written job. And we repeat the maturity point on purpose: Composio has
|
||||
scale and a large user base; we are new. What we offer against that is the source-export
|
||||
exit and a{' '}
|
||||
<Link href="/pricing" className="text-[--color-accent] hover:underline">
|
||||
free tier
|
||||
</Link>{' '}
|
||||
that lets you test the claim in minutes — start from a prompt or fork a{' '}
|
||||
<Link href="/templates" className="text-[--color-accent] hover:underline">
|
||||
template
|
||||
</Link>
|
||||
.
|
||||
</P>
|
||||
|
||||
<P>
|
||||
Related:{' '}
|
||||
<Link
|
||||
href="/guides/mcp-server-hosting-pricing"
|
||||
className="text-[--color-accent] hover:underline"
|
||||
>
|
||||
MCP hosting pricing compared
|
||||
</Link>{' '}
|
||||
·{' '}
|
||||
<Link
|
||||
href="/guides/hosted-mcp-platforms-compared"
|
||||
className="text-[--color-accent] hover:underline"
|
||||
>
|
||||
the four platform categories
|
||||
</Link>{' '}
|
||||
·{' '}
|
||||
<Link href="/guides/mintmcp-alternative" className="text-[--color-accent] hover:underline">
|
||||
MintMCP alternative
|
||||
</Link>
|
||||
.
|
||||
</P>
|
||||
</ArticleShell>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
import { articleOgImage, OG_SIZE } from '@/lib/og-article';
|
||||
|
||||
export const runtime = 'edge';
|
||||
export const alt = 'How to create an MCP server without writing code (2026)';
|
||||
export const size = OG_SIZE;
|
||||
export const contentType = 'image/png';
|
||||
|
||||
export default function Image() {
|
||||
return articleOgImage({
|
||||
title: 'How to create an MCP server without writing code (2026)',
|
||||
tag: 'Guide',
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,222 @@
|
||||
import { JsonLd } from '@/components/json-ld';
|
||||
import { StaticCodeBlock } from '@/components/static-code-block';
|
||||
import { articleJsonLd, breadcrumbJsonLd, faqJsonLd, pageMetadata } from '@/lib/seo';
|
||||
import Link from 'next/link';
|
||||
import { ArticleShell, H2, Note, OL, P, Strong, UL } from '../article-shell';
|
||||
|
||||
const PATH = '/guides/create-mcp-server-without-code';
|
||||
const TITLE = 'How to create an MCP server without writing code (2026)';
|
||||
const DESCRIPTION =
|
||||
'Turn a plain-language description into a hosted, OAuth-protected MCP server — no SDK, no Docker, no TypeScript. What works, what the limits are, and when you still need code.';
|
||||
|
||||
export const metadata = pageMetadata({ title: TITLE, description: DESCRIPTION, path: PATH });
|
||||
|
||||
const ARTICLE_FAQ = [
|
||||
{
|
||||
q: 'Do I really write zero code to create an MCP server?',
|
||||
a: 'Yes — you describe the tools in natural language. The platform generates a TypeScript MCP server, runs static checks against banned patterns, builds a Docker image and deploys it behind OAuth 2.1. You never touch the code unless you want to: the full source is exportable at any time.',
|
||||
},
|
||||
{
|
||||
q: 'How long does generation take?',
|
||||
a: 'Spec to image to live URL typically completes in 45–90 seconds. You watch the build log stream live in the dashboard.',
|
||||
},
|
||||
{
|
||||
q: 'Which AI clients can use a no-code MCP server?',
|
||||
a: 'Anything that speaks the MCP spec over Streamable HTTP: Claude Desktop, Cursor, ChatGPT custom connectors, VS Code Copilot and Continue.dev. You get a copy-paste install snippet for each.',
|
||||
},
|
||||
{
|
||||
q: 'What does it cost to try?',
|
||||
a: 'The free tier includes one hosted server and 100,000 tool calls per month — no credit card. The full TypeScript source of every server you build is exportable, so there is no lock-in.',
|
||||
},
|
||||
];
|
||||
|
||||
const PROMPT_EXAMPLE = `Create an MCP server that searches our Notion workspace.
|
||||
Tools: search_pages, get_page_content.
|
||||
Auth: NOTION_API_KEY.`;
|
||||
|
||||
const SNIPPET_EXAMPLE = `{
|
||||
"mcpServers": {
|
||||
"notion": {
|
||||
"url": "https://notion-x9.mcp.buildmymcpserver.com/mcp",
|
||||
"auth": "oauth2"
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<JsonLd
|
||||
data={articleJsonLd({
|
||||
title: TITLE,
|
||||
description: DESCRIPTION,
|
||||
path: PATH,
|
||||
datePublished: '2026-07-08',
|
||||
authorName: 'Marco Sadjadi',
|
||||
wordCount: 1500,
|
||||
})}
|
||||
/>
|
||||
<JsonLd
|
||||
data={breadcrumbJsonLd([
|
||||
{ name: 'Home', path: '/' },
|
||||
{ name: 'Guides', path: '/guides' },
|
||||
{ name: TITLE, path: PATH },
|
||||
])}
|
||||
/>
|
||||
<JsonLd data={faqJsonLd(ARTICLE_FAQ)} />
|
||||
<ArticleShell
|
||||
title={TITLE}
|
||||
subtitle="The Model Context Protocol lets AI assistants call your tools — but the official route to a server means an SDK, a transport, an auth layer and somewhere to host it. Here is the route that skips all four, and an honest list of where it stops."
|
||||
updated="July 2026"
|
||||
>
|
||||
<H2>What “no code” actually has to cover</H2>
|
||||
<P>
|
||||
An MCP server that works outside your laptop is more than tool functions. To let Claude,
|
||||
Cursor or ChatGPT call it from anywhere, you need four things: the{' '}
|
||||
<Strong>server code</Strong> itself (tool definitions plus handlers), a{' '}
|
||||
<Strong>remote transport</Strong> (Streamable HTTP — the STDIO servers most tutorials
|
||||
build only work locally), an <Strong>auth layer</Strong> (the MCP spec requires OAuth 2.1
|
||||
for remote servers), and <Strong>hosting</Strong> that keeps the process alive. A no-code
|
||||
claim that only generates the first item leaves you with three engineering problems. The
|
||||
flow below covers all four.
|
||||
</P>
|
||||
|
||||
<H2>Step 1 — describe the tool in plain language</H2>
|
||||
<P>
|
||||
You write a prompt, not a spec. Name the tools you want, the credentials they need, and
|
||||
what they should do. A working example:
|
||||
</P>
|
||||
<StaticCodeBlock code={PROMPT_EXAMPLE} label="prompt" />
|
||||
<P>
|
||||
Three lines is enough because the generator asks the model for a structured spec, not
|
||||
prose: tool names, input schemas, the API calls behind them, and which environment
|
||||
variables hold secrets. If the description is ambiguous, you see the interpreted spec
|
||||
before anything builds and can edit it.
|
||||
</P>
|
||||
|
||||
<H2>Step 2 — generation and static checks</H2>
|
||||
<P>
|
||||
From the spec, the platform renders a TypeScript MCP server. Before anything runs, the
|
||||
generated code goes through static checks against banned patterns — no
|
||||
<Strong> child processes, no filesystem escapes, no calls to unlisted hosts</Strong>. This
|
||||
matters more in a no-code flow than in a hand-written one: you did not read the code, so
|
||||
the platform has to.
|
||||
</P>
|
||||
<P>
|
||||
The output is real, exportable TypeScript. If you cancel your account tomorrow, you can{' '}
|
||||
<Link href="/docs/authoring" className="text-[--color-accent] hover:underline">
|
||||
take the source
|
||||
</Link>{' '}
|
||||
and run it yourself — there is no proprietary runtime inside.
|
||||
</P>
|
||||
|
||||
<H2>Step 3 — build and deploy</H2>
|
||||
<P>
|
||||
The server is packaged into a Docker image and deployed to its own isolated container —
|
||||
one container per server, so a bug in your Notion tool cannot see your Stripe tool's
|
||||
credentials. Secrets you provide (like <Strong>NOTION_API_KEY</Strong>) are encrypted
|
||||
with AES-256-GCM at rest and injected as environment variables only at runtime. They are
|
||||
never logged and never echoed back into the dashboard.
|
||||
</P>
|
||||
<P>
|
||||
The whole pipeline — spec, render, checks, image build, deploy — typically completes in{' '}
|
||||
<Strong>45–90 seconds</Strong>, streamed live to the dashboard so you can watch each
|
||||
stage pass.
|
||||
</P>
|
||||
|
||||
<H2>Step 4 — the OAuth-protected URL</H2>
|
||||
<P>
|
||||
The deployed server is a public Streamable HTTP endpoint, but every request is gated by
|
||||
OAuth 2.1 before it reaches your container. The control plane acts as the authorization
|
||||
server — PKCE, Dynamic Client Registration (RFC 7591) and Resource Indicators (RFC 8707)
|
||||
— which is exactly the handshake modern MCP clients expect. You never configure any of
|
||||
it;{' '}
|
||||
<Link href="/docs/oauth" className="text-[--color-accent] hover:underline">
|
||||
the OAuth docs
|
||||
</Link>{' '}
|
||||
explain what happens under the hood.
|
||||
</P>
|
||||
|
||||
<H2>Step 5 — install in your client</H2>
|
||||
<P>The dashboard renders a copy-paste snippet per client. For Claude Desktop:</P>
|
||||
<StaticCodeBlock code={SNIPPET_EXAMPLE} label="claude_desktop_config.json" />
|
||||
<P>
|
||||
On first use the client opens the OAuth consent flow in your browser; approve it once and
|
||||
the tools appear. The same server works in Cursor, ChatGPT custom connectors, VS Code
|
||||
Copilot and Continue.dev — see the client-specific walkthroughs for{' '}
|
||||
<Link href="/guides/claude-desktop-mcp-setup" className="text-[--color-accent] hover:underline">
|
||||
Claude Desktop
|
||||
</Link>{' '}
|
||||
and{' '}
|
||||
<Link href="/guides/chatgpt-mcp-connector" className="text-[--color-accent] hover:underline">
|
||||
ChatGPT
|
||||
</Link>
|
||||
.
|
||||
</P>
|
||||
|
||||
<H2>What you cannot do without code — honest limits</H2>
|
||||
<UL>
|
||||
<li>
|
||||
<Strong>Complex business logic.</Strong> Generation is good at “call this API, shape
|
||||
the response”. Multi-step workflows with branching state, retries with compensation, or
|
||||
heavy data transformation deserve hand-written code. Export the generated source and
|
||||
extend it — that is the intended escape hatch.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Unusual protocols.</Strong> Tools that need gRPC, raw TCP, or binary SDKs are
|
||||
out of scope for prompt-generation; the generated servers speak HTTP to upstream APIs.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Long-running jobs.</Strong> A tool call is a request/response. Anything that
|
||||
takes minutes belongs in a queue you own, with the MCP tool submitting and polling.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Compliance paperwork.</Strong> If procurement requires SOC 2 or HIPAA
|
||||
certification today, a generated-and-hosted server will not check that box — no such
|
||||
certifications are claimed. Self-hosting the exported source inside your own audited
|
||||
infrastructure is the workaround.
|
||||
</li>
|
||||
</UL>
|
||||
<Note>
|
||||
Rule of thumb: if you can describe the tool in three sentences, generation gets you to
|
||||
production. If your description needs a diagram, write code — or generate first, export,
|
||||
then edit.
|
||||
</Note>
|
||||
|
||||
<H2>Try it against a real API first</H2>
|
||||
<OL>
|
||||
<li>
|
||||
Pick one API you use daily — Notion, GitHub, your own REST backend (
|
||||
<Link href="/guides/rest-api-to-mcp-server" className="text-[--color-accent] hover:underline">
|
||||
wrapping a REST API
|
||||
</Link>{' '}
|
||||
is the most common first server).
|
||||
</li>
|
||||
<li>Write the three-line prompt: tools, credentials, behavior.</li>
|
||||
<li>Watch the build, paste the snippet, ask your assistant to use the tool.</li>
|
||||
</OL>
|
||||
<P>
|
||||
The{' '}
|
||||
<Link href="/pricing" className="text-[--color-accent] hover:underline">
|
||||
free tier
|
||||
</Link>{' '}
|
||||
covers one server and 100,000 tool calls a month, which is more than enough to find out
|
||||
whether the no-code route fits your case. If it does not, you have lost ten minutes and
|
||||
gained a working reference implementation to export. Browse{' '}
|
||||
<Link href="/templates" className="text-[--color-accent] hover:underline">
|
||||
the template gallery
|
||||
</Link>{' '}
|
||||
if you would rather fork a working server than write a prompt.
|
||||
</P>
|
||||
|
||||
<H2>FAQ</H2>
|
||||
{ARTICLE_FAQ.map((f) => (
|
||||
<div key={f.q} className="mt-5">
|
||||
<h3 className="text-[15px] font-semibold tracking-tight text-[--color-fg]">{f.q}</h3>
|
||||
<p className="mt-1.5 text-[14px] leading-relaxed text-[--color-fg-muted]">{f.a}</p>
|
||||
</div>
|
||||
))}
|
||||
</ArticleShell>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
import { articleOgImage, OG_SIZE } from '@/lib/og-article';
|
||||
|
||||
export const runtime = 'edge';
|
||||
export const alt = 'OAuth 2.1 for MCP servers: PKCE, DCR and RFC 8707 in plain English';
|
||||
export const size = OG_SIZE;
|
||||
export const contentType = 'image/png';
|
||||
|
||||
export default function Image() {
|
||||
return articleOgImage({
|
||||
title: 'OAuth 2.1 for MCP servers, in plain English',
|
||||
tag: 'Explainer',
|
||||
});
|
||||
}
|
||||
180
apps/web/app/(marketing)/guides/mcp-oauth-plain-english/page.tsx
Normal file
180
apps/web/app/(marketing)/guides/mcp-oauth-plain-english/page.tsx
Normal file
@ -0,0 +1,180 @@
|
||||
import { JsonLd } from '@/components/json-ld';
|
||||
import { articleJsonLd, breadcrumbJsonLd, pageMetadata } from '@/lib/seo';
|
||||
import Link from 'next/link';
|
||||
import { ArticleShell, H2, Note, OL, P, Strong, UL } from '../article-shell';
|
||||
|
||||
const PATH = '/guides/mcp-oauth-plain-english';
|
||||
const TITLE = 'OAuth 2.1 for MCP servers: PKCE, DCR and RFC 8707 in plain English';
|
||||
const DESCRIPTION =
|
||||
'The three RFCs behind MCP authorization — PKCE, Dynamic Client Registration and Resource Indicators — explained without jargon: what each one does, the full flow step by step, and what breaks when you skip one.';
|
||||
|
||||
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-07-08',
|
||||
authorName: 'Marco Sadjadi',
|
||||
wordCount: 1400,
|
||||
})}
|
||||
/>
|
||||
<JsonLd
|
||||
data={breadcrumbJsonLd([
|
||||
{ name: 'Home', path: '/' },
|
||||
{ name: 'Guides', path: '/guides' },
|
||||
{ name: TITLE, path: PATH },
|
||||
])}
|
||||
/>
|
||||
<ArticleShell
|
||||
title={TITLE}
|
||||
subtitle="MCP authorization is OAuth 2.1 plus three RFCs that most OAuth tutorials never mention. This is the concept explainer — what each piece does and why the spec requires it. For the deployment how-to, see the hosting guide."
|
||||
updated="July 2026"
|
||||
>
|
||||
<H2>Why MCP needs more than classic OAuth</H2>
|
||||
<P>
|
||||
Classic OAuth assumes you know your clients in advance: you register an app in a
|
||||
dashboard, get a <code>client_id</code> and <code>client_secret</code>, and ship them
|
||||
inside your application. MCP breaks both assumptions. The client is someone's Claude
|
||||
Desktop or Cursor install — you will never pre-register it — and it runs on a desktop
|
||||
where a baked-in secret is not a secret. OAuth 2.1 plus three extension RFCs is how the
|
||||
MCP spec resolves this.
|
||||
</P>
|
||||
|
||||
<H2>The two roles: Authorization Server and Resource Server</H2>
|
||||
<P>
|
||||
Every OAuth setup splits into two jobs. The <Strong>Authorization Server (AS)</Strong>{' '}
|
||||
authenticates users and issues tokens. The <Strong>Resource Server (RS)</Strong> — your
|
||||
MCP server — accepts requests only when they carry a valid token. They can be the same
|
||||
deployment or separate services; the protocol only cares that the RS can verify what the
|
||||
AS signs, typically via a published JWKS (the AS's public keys).
|
||||
</P>
|
||||
|
||||
<H2>PKCE (RFC 7636): proof that the token requester started the flow</H2>
|
||||
<P>
|
||||
The authorization-code flow hands the client a one-time code via a browser redirect, and
|
||||
the client exchanges that code for a token. The classic attack is stealing the code
|
||||
in-flight and exchanging it yourself. <Strong>PKCE</Strong> closes this: the client
|
||||
invents a random secret (the verifier), sends only its hash (the challenge) when the flow
|
||||
starts, and must present the original verifier at exchange time. A thief who intercepted
|
||||
the code never saw the verifier, so the code is useless to them.
|
||||
</P>
|
||||
<P>
|
||||
In OAuth 2.1, PKCE is <Strong>mandatory for every client</Strong> — the old exemption for
|
||||
"confidential" server-side clients is gone. If your AS treats PKCE as optional, it is not
|
||||
OAuth 2.1.
|
||||
</P>
|
||||
|
||||
<H2>Dynamic Client Registration (RFC 7591): clients register themselves</H2>
|
||||
<P>
|
||||
When a user adds your MCP server to Claude Desktop, the client calls your AS's
|
||||
registration endpoint at runtime — "here is my name and redirect URI" — and receives a
|
||||
fresh <code>client_id</code> on the spot. No dashboard, no support ticket, no shared
|
||||
credentials between users. <Strong>DCR</Strong> is what makes "paste a URL, connect,
|
||||
done" possible: without it, every user of every MCP client would need you to manually
|
||||
provision app credentials.
|
||||
</P>
|
||||
|
||||
<H2>Resource Indicators (RFC 8707): tokens bound to one server</H2>
|
||||
<P>
|
||||
A user might connect their client to ten different MCP servers. Without{' '}
|
||||
<Strong>Resource Indicators</Strong>, a token minted for server A could be replayed
|
||||
against server B — any server you talk to could impersonate you elsewhere. RFC 8707 has
|
||||
the client name the exact server (the <code>resource</code>) when requesting the token,
|
||||
and the AS bakes that audience into the token. Your MCP server then rejects any token
|
||||
whose audience is not itself. One token, one server, no cross-server replay.
|
||||
</P>
|
||||
|
||||
<H2>The full flow, step by step</H2>
|
||||
<OL>
|
||||
<li>
|
||||
The client sends an unauthenticated request to your MCP server and gets a{' '}
|
||||
<Strong>401</Strong> with a <code>WWW-Authenticate</code> header pointing at the
|
||||
protected-resource metadata — this is how the client discovers your AS.
|
||||
</li>
|
||||
<li>The client registers itself with the AS via DCR and receives a client_id.</li>
|
||||
<li>
|
||||
The client generates a PKCE verifier + challenge and opens the browser to the AS's
|
||||
authorize endpoint, naming your server as the <code>resource</code>.
|
||||
</li>
|
||||
<li>The user signs in and approves; the AS redirects back with a one-time code.</li>
|
||||
<li>
|
||||
The client exchanges code + PKCE verifier for an access token that is
|
||||
audience-bound to your server.
|
||||
</li>
|
||||
<li>
|
||||
Every subsequent MCP request carries the token; your server verifies signature, expiry
|
||||
and audience on each call.
|
||||
</li>
|
||||
</OL>
|
||||
|
||||
<H2>What breaks when you skip a piece</H2>
|
||||
<UL>
|
||||
<li>
|
||||
<Strong>No protected-resource metadata / wrong 401</Strong> — clients can't discover
|
||||
your AS. The symptom: the server "works" with curl but silently fails to install from
|
||||
Claude Desktop. This is the most common failure in the wild.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>No DCR</Strong> — the connect flow dead-ends at "unknown client" for anyone but
|
||||
you.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>No PKCE (or PKCE accepted but not enforced)</Strong> — the flow appears to work
|
||||
but intercepted authorization codes become exchangeable. Invisible until exploited.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>No audience check (RFC 8707)</Strong> — tokens for other servers are accepted
|
||||
by yours, and yours are accepted elsewhere. Also invisible until exploited.
|
||||
</li>
|
||||
</UL>
|
||||
<Note>
|
||||
The failure modes split into two families: discovery mistakes are loud (nothing
|
||||
connects), security mistakes are silent (everything connects, including attackers). Test
|
||||
for both — an end-to-end install from a real client proves discovery, but only negative
|
||||
tests (expired token, wrong audience, missing PKCE) prove the security half.
|
||||
</Note>
|
||||
|
||||
<H2>Common implementation mistakes</H2>
|
||||
<UL>
|
||||
<li>Substring-matching redirect URIs instead of exact-match comparison.</li>
|
||||
<li>Allowing authorization codes to be exchanged more than once.</li>
|
||||
<li>Accepting <code>plain</code> PKCE instead of requiring <code>S256</code>.</li>
|
||||
<li>
|
||||
Verifying token signature and expiry but forgetting the audience claim — RFC 8707 only
|
||||
protects you if the RS actually checks it.
|
||||
</li>
|
||||
<li>
|
||||
Long-lived access tokens as a substitute for refresh tokens — shorter access-token
|
||||
lifetime plus refresh is the OAuth 2.1 posture.
|
||||
</li>
|
||||
</UL>
|
||||
|
||||
<H2>Build it or get it built-in</H2>
|
||||
<P>
|
||||
None of this is exotic, but it is a genuine authorization-server implementation — days of
|
||||
work plus ongoing spec-tracking, and mistakes are security bugs rather than build
|
||||
failures. If you want to own it, the{' '}
|
||||
<Link
|
||||
href="/guides/host-mcp-server-with-oauth"
|
||||
className="text-[--color-accent] hover:underline"
|
||||
>
|
||||
hosting guide
|
||||
</Link>{' '}
|
||||
walks through the deployment options. If you'd rather not: every server generated on
|
||||
BuildMyMCPServer ships behind an OAuth 2.1 AS with PKCE, DCR and Resource Indicators
|
||||
already wired — the flow above is what our{' '}
|
||||
<Link href="/docs/oauth" className="text-[--color-accent] hover:underline">
|
||||
OAuth docs
|
||||
</Link>{' '}
|
||||
implement, and the <Link href="/pricing" className="text-[--color-accent] hover:underline">free tier</Link>{' '}
|
||||
includes it.
|
||||
</P>
|
||||
</ArticleShell>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
import { articleOgImage, OG_SIZE } from '@/lib/og-article';
|
||||
|
||||
export const runtime = 'edge';
|
||||
export const alt = 'MCP server hosting: pricing & options compared (2026)';
|
||||
export const size = OG_SIZE;
|
||||
export const contentType = 'image/png';
|
||||
|
||||
export default function Image() {
|
||||
return articleOgImage({
|
||||
title: 'MCP server hosting: pricing & options compared (2026)',
|
||||
tag: 'Comparison',
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,251 @@
|
||||
import { JsonLd } from '@/components/json-ld';
|
||||
import { articleJsonLd, breadcrumbJsonLd, faqJsonLd, pageMetadata } from '@/lib/seo';
|
||||
import Link from 'next/link';
|
||||
import { ArticleShell, H2, Note, P, Strong, Table, UL } from '../article-shell';
|
||||
|
||||
const PATH = '/guides/mcp-server-hosting-pricing';
|
||||
const TITLE = 'MCP server hosting: pricing & options compared (2026)';
|
||||
const DESCRIPTION =
|
||||
'What hosting a remote MCP server actually costs in 2026 — Cloudflare Workers, Smithery, Composio, MintMCP and prompt-to-server generation, compared on price, effort and lock-in.';
|
||||
|
||||
export const metadata = pageMetadata({ title: TITLE, description: DESCRIPTION, path: PATH });
|
||||
|
||||
// Pricing FAQ rendered below AND emitted as FAQPage JSON-LD — single source.
|
||||
const PRICING_FAQ = [
|
||||
{
|
||||
q: 'What is the cheapest way to host an MCP server?',
|
||||
a: 'If you can write the code yourself: Cloudflare Workers — the free tier covers 100k requests/day, which is more than most personal MCP servers ever see. If you cannot or do not want to write the code, generator platforms start free (BuildMyMCPServer Hobby: 1 server, 100k tool calls/month at €0).',
|
||||
},
|
||||
{
|
||||
q: 'Do I pay per tool call or per server?',
|
||||
a: 'Both models exist. Composio bills per tool call across its catalog. Cloudflare bills per request/CPU-time. BuildMyMCPServer prices tiers by server count plus a monthly tool-call allowance. Read the overage terms — per-call platforms get expensive at agent-scale traffic.',
|
||||
},
|
||||
{
|
||||
q: 'Is there a free way to get an OAuth-protected remote MCP server?',
|
||||
a: 'Yes, two honest routes: build it yourself on Cloudflare Workers with their OAuth provider library (free tier, your time is the cost), or generate one on a free tier of a hosting generator (BuildMyMCPServer Hobby is €0 for one server with OAuth 2.1 included).',
|
||||
},
|
||||
{
|
||||
q: 'What should enterprises check before picking an MCP host?',
|
||||
a: 'Compliance posture (SOC 2 / HIPAA — MintMCP leads here as of mid-2026), data residency, audit logging, SSO, and an exit path. Ask every vendor: can I export the server code and leave?',
|
||||
},
|
||||
];
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<JsonLd
|
||||
data={articleJsonLd({
|
||||
title: TITLE,
|
||||
description: DESCRIPTION,
|
||||
path: PATH,
|
||||
datePublished: '2026-07-08',
|
||||
authorName: 'Marco Sadjadi',
|
||||
wordCount: 1350,
|
||||
})}
|
||||
/>
|
||||
<JsonLd
|
||||
data={breadcrumbJsonLd([
|
||||
{ name: 'Home', path: '/' },
|
||||
{ name: 'Guides', path: '/guides' },
|
||||
{ name: TITLE, path: PATH },
|
||||
])}
|
||||
/>
|
||||
<JsonLd data={faqJsonLd(PRICING_FAQ)} />
|
||||
<ArticleShell
|
||||
title={TITLE}
|
||||
subtitle="Five ways to put an MCP server on the internet, five very different bills. This is the pricing landscape as of July 2026 — including where each option quietly gets expensive, and where the free tiers are genuinely free."
|
||||
updated="July 2026"
|
||||
>
|
||||
<H2>The five options at a glance</H2>
|
||||
<P>
|
||||
"MCP server hosting" covers products that do very different jobs: raw compute you
|
||||
deploy to, directories that also host, connector catalogs that bill per call, wrappers
|
||||
that productionize a server you already wrote, and generators that write and host the
|
||||
server for you. Comparing them on price only makes sense once you know which job you are
|
||||
buying.
|
||||
</P>
|
||||
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Platform</th>
|
||||
<th>What you get</th>
|
||||
<th>Entry price</th>
|
||||
<th>Paid from</th>
|
||||
<th>You bring</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<Strong>Cloudflare Workers</Strong>
|
||||
</td>
|
||||
<td>Edge compute + OAuth provider library; you build and deploy</td>
|
||||
<td>Free — 100k requests/day</td>
|
||||
<td>$5/mo</td>
|
||||
<td>All the code, OAuth wiring, upkeep</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Strong>Smithery</Strong>
|
||||
</td>
|
||||
<td>Registry of 6,000+ servers, optional hosting of listed servers</td>
|
||||
<td>Free to browse/publish</td>
|
||||
<td>Paid tiers for hosting/usage</td>
|
||||
<td>An existing server (or pick one from the catalog)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Strong>Composio</Strong>
|
||||
</td>
|
||||
<td>1,000+ pre-built SaaS integrations exposed as MCP, managed auth</td>
|
||||
<td>Free — 20k tool calls/mo</td>
|
||||
<td>$29/mo (200k calls)</td>
|
||||
<td>Nothing — but only their catalog, per-call billing</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Strong>MintMCP</Strong>
|
||||
</td>
|
||||
<td>Wraps your existing STDIO server into a remote OAuth deployment; SOC 2 Type II</td>
|
||||
<td>Custom / enterprise pricing</td>
|
||||
<td>Contact sales</td>
|
||||
<td>A working server; budget for enterprise pricing</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Strong>BuildMyMCPServer</Strong>
|
||||
</td>
|
||||
<td>Generates the TypeScript server from a prompt, hosts it behind OAuth 2.1</td>
|
||||
<td>Free — 1 server, 100k tool calls/mo</td>
|
||||
<td>€49/mo (5 servers, 1M calls)</td>
|
||||
<td>A description of the tool. That's it.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
|
||||
<Note>
|
||||
Competitor prices are as of mid-2026 and change often — treat the entry-tier shapes as the
|
||||
durable signal, not the exact numbers. Sources: public pricing pages of each vendor.
|
||||
</Note>
|
||||
|
||||
<H2>Cloudflare Workers: cheapest if your time is free</H2>
|
||||
<P>
|
||||
The free tier (100k requests/day, KV, D1, Durable Objects allowances) comfortably runs a
|
||||
personal or small-team MCP server at <Strong>€0 forever</Strong>, and the $5/mo paid plan
|
||||
covers almost anything below serious production traffic. The catch is that the price tag
|
||||
measures compute, not effort: you write the server, wire the OAuth provider library,
|
||||
handle token refresh edge cases, and own every upgrade when the MCP spec moves. For a
|
||||
team with engineers who enjoy that work, it is the best deal on this page.
|
||||
</P>
|
||||
|
||||
<H2>Smithery: pay for distribution, not development</H2>
|
||||
<P>
|
||||
Smithery is primarily a registry — thousands of community servers, CLI install, and
|
||||
hosting for servers published there, with OAuth handled for hosted listings. Browsing and
|
||||
publishing are free; hosted execution moves to paid tiers with usage. It answers
|
||||
"where do I find or distribute a server" more than "who builds mine" —
|
||||
if the tool you need already exists in the catalog, this can be the fastest free path of
|
||||
all.
|
||||
</P>
|
||||
|
||||
<H2>Composio: per-call pricing for a pre-built catalog</H2>
|
||||
<P>
|
||||
Composio's model is different: you are not hosting <em>your</em> server, you are
|
||||
calling <em>their</em> catalog of 1,000+ integrations with managed auth. Free covers 20k
|
||||
tool calls/month; $29/mo buys 200k, $229/mo buys 2M, with per-call overage beyond. For
|
||||
mainstream SaaS (Gmail, Slack, Salesforce) it is excellent value. The pricing risk is
|
||||
agent-scale traffic — an autonomous agent hammering tools burns per-call budgets fast —
|
||||
and the structural limit is the catalog: your internal API is not in it. More in our{' '}
|
||||
<Link href="/guides/composio-alternative" className="text-[--color-accent] hover:underline">
|
||||
Composio alternative guide
|
||||
</Link>
|
||||
.
|
||||
</P>
|
||||
|
||||
<H2>MintMCP: compliance has a price tag</H2>
|
||||
<P>
|
||||
MintMCP wraps an existing STDIO server into a production remote deployment — OAuth
|
||||
brokering, SSO, SCIM, audit trails, SOC 2 Type II, HIPAA-aligned controls. Pricing is
|
||||
custom/enterprise as of mid-2026. If your buyer is a compliance team, this is the honest
|
||||
shortlist leader; nothing else in this table carries that certification set. If you are a
|
||||
solo developer, it is not aimed at you.
|
||||
</P>
|
||||
|
||||
<H2>BuildMyMCPServer: pay for the whole job, skip the build</H2>
|
||||
<P>
|
||||
Our own slot in this table, stated plainly: you describe the tool in natural language, the
|
||||
platform generates the TypeScript server, runs static checks, builds a container and
|
||||
deploys it behind a full OAuth 2.1 authorization server (PKCE, Dynamic Client
|
||||
Registration, Resource Indicators). <Strong>Hobby is €0</Strong> — one server, 100k tool
|
||||
calls/month. <Strong>Pro is €49/mo</Strong> for 5 servers and 1M calls;{' '}
|
||||
<Strong>Team €199/mo</Strong> adds audit logging at 25 servers/10M calls; Enterprise is
|
||||
custom. Full source export on every tier, so the exit path is real. What we do not have:
|
||||
SOC 2 (we are a young product), a giant connector catalog, or edge PoPs on six continents.
|
||||
See{' '}
|
||||
<Link href="/pricing" className="text-[--color-accent] hover:underline">
|
||||
full pricing
|
||||
</Link>{' '}
|
||||
and the{' '}
|
||||
<Link href="/templates" className="text-[--color-accent] hover:underline">
|
||||
template gallery
|
||||
</Link>
|
||||
.
|
||||
</P>
|
||||
|
||||
<H2>Choosing by scenario</H2>
|
||||
<UL>
|
||||
<li>
|
||||
<Strong>Solo dev, comfortable writing TypeScript, hobby traffic:</Strong> Cloudflare
|
||||
Workers free tier. Unbeatable if you enjoy the build.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Need Gmail/Slack/Notion-class connectors this afternoon:</Strong> Composio free
|
||||
tier, watch the per-call meter as agents scale.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Have a server, need enterprise compliance sign-off:</Strong> MintMCP, budget for
|
||||
enterprise pricing.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Want to publish or discover community servers:</Strong> Smithery.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Need a bespoke tool hosted with OAuth, no code, this hour:</Strong> a generator
|
||||
— that is the job{' '}
|
||||
<Link href="/" className="text-[--color-accent] hover:underline">
|
||||
BuildMyMCPServer
|
||||
</Link>{' '}
|
||||
exists for.
|
||||
</li>
|
||||
</UL>
|
||||
|
||||
<H2>Pricing FAQ</H2>
|
||||
{PRICING_FAQ.map((f) => (
|
||||
<div key={f.q} className="mt-5">
|
||||
<h3 className="text-[15px] font-semibold tracking-tight text-[--color-fg]">{f.q}</h3>
|
||||
<p className="mt-1.5 text-[14px] leading-relaxed text-[--color-fg-muted]">{f.a}</p>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<P>
|
||||
Related:{' '}
|
||||
<Link
|
||||
href="/guides/hosted-mcp-platforms-compared"
|
||||
className="text-[--color-accent] hover:underline"
|
||||
>
|
||||
the four categories of MCP platform, explained
|
||||
</Link>{' '}
|
||||
·{' '}
|
||||
<Link
|
||||
href="/guides/host-mcp-server-with-oauth"
|
||||
className="text-[--color-accent] hover:underline"
|
||||
>
|
||||
what hosting with OAuth actually involves
|
||||
</Link>
|
||||
.
|
||||
</P>
|
||||
</ArticleShell>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
import { articleOgImage, OG_SIZE } from '@/lib/og-article';
|
||||
|
||||
export const runtime = 'edge';
|
||||
export const alt = 'MCP Server ohne Code erstellen und hosten (2026)';
|
||||
export const size = OG_SIZE;
|
||||
export const contentType = 'image/png';
|
||||
|
||||
export default function Image() {
|
||||
return articleOgImage({
|
||||
title: 'MCP Server ohne Code erstellen und hosten',
|
||||
tag: 'Anleitung',
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,176 @@
|
||||
import { JsonLd } from '@/components/json-ld';
|
||||
import { StaticCodeBlock } from '@/components/static-code-block';
|
||||
import { articleJsonLd, breadcrumbJsonLd, pageMetadata } from '@/lib/seo';
|
||||
import Link from 'next/link';
|
||||
import { ArticleShell, H2, Note, OL, P, Strong, UL } from '../article-shell';
|
||||
|
||||
const PATH = '/guides/mcp-server-ohne-code-erstellen';
|
||||
const TITLE = 'MCP Server ohne Code erstellen und hosten (2026)';
|
||||
const DESCRIPTION =
|
||||
'Wie Sie ohne Programmierkenntnisse einen eigenen MCP Server erstellen und hosten: Tool auf Deutsch oder Englisch beschreiben, generierten TypeScript-Server mit OAuth 2.1 deployen, Install-Snippet in Claude, Cursor oder ChatGPT einfügen.';
|
||||
|
||||
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-07-08',
|
||||
authorName: 'Marco Sadjadi',
|
||||
wordCount: 1250,
|
||||
inLanguage: 'de',
|
||||
})}
|
||||
/>
|
||||
<JsonLd
|
||||
data={breadcrumbJsonLd([
|
||||
{ name: 'Home', path: '/' },
|
||||
{ name: 'Guides', path: '/guides' },
|
||||
{ name: TITLE, path: PATH },
|
||||
])}
|
||||
/>
|
||||
<ArticleShell
|
||||
title={TITLE}
|
||||
subtitle="MCP verbindet KI-Assistenten wie Claude, Cursor und ChatGPT mit Ihren eigenen Tools und Daten. Dieser Guide zeigt den Weg vom Satz in natürlicher Sprache zum gehosteten, OAuth-geschützten Server — ohne eine Zeile Code."
|
||||
updated="Juli 2026"
|
||||
>
|
||||
<H2>Was ist MCP — in zwei Absätzen</H2>
|
||||
<P>
|
||||
Das <Strong>Model Context Protocol (MCP)</Strong> ist ein offener Standard von Anthropic,
|
||||
der KI-Assistenten mit externen Tools, Datenbanken und APIs verbindet. Statt für jeden
|
||||
Assistenten eine eigene Integration zu bauen, stellen Sie einen MCP Server bereit — und
|
||||
jeder kompatible Client (Claude Desktop, Cursor, ChatGPT, VS Code Copilot, Continue.dev)
|
||||
kann ihn nutzen.
|
||||
</P>
|
||||
<P>
|
||||
Ein MCP Server stellt <Strong>Tools</Strong> bereit: klar definierte Funktionen wie
|
||||
„durchsuche unser Notion-Workspace" oder „lies Bestellungen aus der Datenbank". Die KI
|
||||
entscheidet im Gespräch, wann sie ein Tool aufruft; der Server führt aus und liefert das
|
||||
Ergebnis zurück in den Chat.
|
||||
</P>
|
||||
|
||||
<H2>Warum „ohne Code" bisher nicht selbstverständlich war</H2>
|
||||
<P>
|
||||
Einen lokalen Test-Server bekommt man mit dem MCP-SDK schnell hin — wenn man
|
||||
programmieren kann. Ein <Strong>produktiver, erreichbarer</Strong> Server ist ein anderes
|
||||
Kaliber: Er braucht den Streamable-HTTP-Transport, TLS, eine OAuth-2.1-Autorisierung
|
||||
(damit nicht das ganze Internet Ihre Tools aufrufen kann), sichere Verwahrung Ihrer
|
||||
API-Schlüssel und Hosting, das nicht nach zwei Wochen umfällt. Genau diese Schicht können
|
||||
Sie heute generieren lassen, statt sie zu bauen.
|
||||
</P>
|
||||
|
||||
<H2>Der Weg: von der Beschreibung zum laufenden Server</H2>
|
||||
<OL>
|
||||
<li>
|
||||
<Strong>Tool beschreiben — auf Deutsch oder Englisch.</Strong> Ein präziser Satz
|
||||
genügt: welche Aufgabe, welche Tools, welche Zugangsdaten. Beispiel unten.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Spezifikation prüfen.</Strong> Die Plattform (in unserem Fall
|
||||
BuildMyMCPServer) analysiert die Beschreibung und schlägt die Tool-Definitionen vor —
|
||||
Namen, Parameter, benötigte Secrets. Hier korrigieren Sie, bevor etwas gebaut wird.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Generieren und deployen lassen.</Strong> Daraus entsteht ein
|
||||
TypeScript-MCP-Server, der statisch geprüft, in ein Docker-Image gebaut und auf einer
|
||||
eigenen Subdomain deployt wird — hinter einem OAuth-2.1-Authorization-Server mit PKCE.
|
||||
Typische Dauer: 45–90 Sekunden.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Secrets eintragen.</Strong> API-Schlüssel (z. B. Ihr Notion-Token) werden
|
||||
AES-256-GCM-verschlüsselt gespeichert und nur zur Laufzeit als Umgebungsvariablen in
|
||||
den Container injiziert — sie landen nie im Code und nie in Logs.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Install-Snippet in den Client kopieren.</Strong> Für Claude Desktop, Cursor
|
||||
und ChatGPT gibt es fertige Snippets; beim ersten Zugriff läuft der OAuth-Flow im
|
||||
Browser.
|
||||
</li>
|
||||
</OL>
|
||||
|
||||
<H2>Ein konkretes Beispiel</H2>
|
||||
<P>So sieht eine ausreichende Beschreibung aus — Deutsch funktioniert:</P>
|
||||
<StaticCodeBlock
|
||||
label="Prompt"
|
||||
code={`Erstelle einen MCP Server, der unser Notion-Workspace durchsucht.
|
||||
Tools: search_pages, get_page_content.
|
||||
Auth: NOTION_API_KEY.`}
|
||||
/>
|
||||
<P>Und so das Ergebnis im Client — ein Eintrag in der Konfiguration von Claude Desktop:</P>
|
||||
<StaticCodeBlock
|
||||
label="claude_desktop_config.json"
|
||||
code={`{
|
||||
"mcpServers": {
|
||||
"notion": {
|
||||
"url": "https://notion-x9.mcp.buildmymcpserver.com/mcp",
|
||||
"auth": "oauth2"
|
||||
}
|
||||
}
|
||||
}`}
|
||||
/>
|
||||
|
||||
<H2>Was Sie trotzdem verstehen sollten</H2>
|
||||
<P>
|
||||
„Ohne Code" heisst nicht „ohne Verantwortung". Drei Dinge bleiben Ihre Entscheidung:
|
||||
</P>
|
||||
<UL>
|
||||
<li>
|
||||
<Strong>Rechteumfang der Schlüssel.</Strong> Geben Sie dem Server nur die Rechte, die
|
||||
die Tools brauchen — ein Read-only-Schlüssel, wo die Quelle das anbietet. Die KI wird
|
||||
jedes Tool nutzen, das existiert.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Welche Tools existieren.</Strong> Lesende Tools zuerst; schreibende Tools nur,
|
||||
wenn der Anwendungsfall sie wirklich verlangt. Mehr dazu in unserer{' '}
|
||||
<Link
|
||||
href="/guides/mcp-server-security-checklist"
|
||||
className="text-[--color-accent] hover:underline"
|
||||
>
|
||||
Security-Checkliste
|
||||
</Link>{' '}
|
||||
(Englisch).
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Wem Sie den Betrieb anvertrauen.</Strong> Prüfen Sie die Sicherheitsangaben
|
||||
des Anbieters auf Konkretes: Verschlüsselung, Container-Isolation, Quellcode-Export.
|
||||
Bei uns steht das auf der <Link href="/security" className="text-[--color-accent] hover:underline">Security-Seite</Link>{' '}
|
||||
— und jeden generierten Server können Sie als TypeScript-Quellcode exportieren und
|
||||
selbst hosten. Kein Lock-in.
|
||||
</li>
|
||||
</UL>
|
||||
|
||||
<H2>Kosten</H2>
|
||||
<P>
|
||||
Der Einstieg ist kostenlos: Der Hobby-Plan umfasst einen Server mit 100 000
|
||||
Tool-Aufrufen pro Monat auf einer BuildMyMCP-Subdomain. Bezahlpläne skalieren über
|
||||
Server-Anzahl und Aufruf-Volumen — Details auf der{' '}
|
||||
<Link href="/pricing" className="text-[--color-accent] hover:underline">
|
||||
Preisseite
|
||||
</Link>
|
||||
.
|
||||
</P>
|
||||
|
||||
<Note>
|
||||
Für die technischen Hintergründe auf Englisch:{' '}
|
||||
<Link
|
||||
href="/guides/mcp-transports-explained"
|
||||
className="text-[--color-accent] hover:underline"
|
||||
>
|
||||
MCP-Transporte erklärt
|
||||
</Link>{' '}
|
||||
(warum Streamable HTTP der Standard ist) und{' '}
|
||||
<Link
|
||||
href="/guides/mcp-oauth-plain-english"
|
||||
className="text-[--color-accent] hover:underline"
|
||||
>
|
||||
OAuth 2.1 für MCP Server
|
||||
</Link>{' '}
|
||||
(was PKCE, DCR und RFC 8707 leisten).
|
||||
</Note>
|
||||
</ArticleShell>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
import { articleOgImage, OG_SIZE } from '@/lib/og-article';
|
||||
|
||||
export const runtime = 'edge';
|
||||
export const alt = 'MCP server security checklist: secrets, isolation, auth';
|
||||
export const size = OG_SIZE;
|
||||
export const contentType = 'image/png';
|
||||
|
||||
export default function Image() {
|
||||
return articleOgImage({
|
||||
title: 'MCP server security checklist: secrets, isolation, auth',
|
||||
tag: 'Checklist',
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,192 @@
|
||||
import { JsonLd } from '@/components/json-ld';
|
||||
import { articleJsonLd, breadcrumbJsonLd, pageMetadata } from '@/lib/seo';
|
||||
import Link from 'next/link';
|
||||
import { ArticleShell, H2, Note, P, Strong, UL } from '../article-shell';
|
||||
|
||||
const PATH = '/guides/mcp-server-security-checklist';
|
||||
const TITLE = 'MCP server security checklist: secrets, isolation, auth';
|
||||
const DESCRIPTION =
|
||||
'A practical security checklist for production MCP servers: transport auth, secret storage and injection, container isolation, least-privilege tool design, safe logging, and the prompt-injection surface of tool descriptions.';
|
||||
|
||||
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-07-08',
|
||||
authorName: 'Marco Sadjadi',
|
||||
wordCount: 1300,
|
||||
})}
|
||||
/>
|
||||
<JsonLd
|
||||
data={breadcrumbJsonLd([
|
||||
{ name: 'Home', path: '/' },
|
||||
{ name: 'Guides', path: '/guides' },
|
||||
{ name: TITLE, path: PATH },
|
||||
])}
|
||||
/>
|
||||
<ArticleShell
|
||||
title={TITLE}
|
||||
subtitle="An MCP server is an API that an AI calls with credentials you gave it. That combination — machine-driven calls, real secrets, natural-language control — has its own failure modes. Here is the checklist we hold our own platform to."
|
||||
updated="July 2026"
|
||||
>
|
||||
<H2>1. Transport and authentication</H2>
|
||||
<UL>
|
||||
<li>
|
||||
<Strong>TLS everywhere.</Strong> A remote MCP server speaks Streamable HTTP over HTTPS
|
||||
at a stable URL — no plaintext fallback, no self-signed shortcuts in production.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Every request authenticated before it reaches tool code.</Strong> OAuth 2.1
|
||||
with PKCE, Dynamic Client Registration and audience-bound tokens (RFC 8707) is the MCP
|
||||
standard — the details are in{' '}
|
||||
<Link
|
||||
href="/guides/mcp-oauth-plain-english"
|
||||
className="text-[--color-accent] hover:underline"
|
||||
>
|
||||
our plain-English OAuth explainer
|
||||
</Link>
|
||||
. The important checklist item: an unauthenticated request must be rejected by the
|
||||
gateway, not by convention inside your tool logic.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Verify audience, not just signature.</Strong> A token minted for someone else's
|
||||
server must not work on yours. Signature + expiry + audience — all three, every call.
|
||||
</li>
|
||||
</UL>
|
||||
|
||||
<H2>2. Secret storage and injection</H2>
|
||||
<UL>
|
||||
<li>
|
||||
<Strong>Encrypted at rest.</Strong> API keys your server needs (Notion tokens, database
|
||||
DSNs, Stripe keys) belong in a store that is encrypted with a real scheme —
|
||||
AES-256-GCM, not base64, not "it's an internal database".
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Injected at runtime, never baked in.</Strong> Secrets should enter the process
|
||||
as environment variables at container start — never committed into the generated code,
|
||||
the image, or the template you share.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Never echoed back.</Strong> No tool response, error message, build log or
|
||||
debug output should ever contain a secret value. Test this deliberately: ask the AI
|
||||
client to print its configuration and confirm the secret does not appear.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Scoped upstream credentials.</Strong> If the upstream API offers read-only or
|
||||
resource-scoped keys, use them. The MCP server can only leak the power you gave it.
|
||||
</li>
|
||||
</UL>
|
||||
|
||||
<H2>3. Isolation between servers</H2>
|
||||
<P>
|
||||
If you run more than one MCP server — or host servers for more than one user — the
|
||||
blast radius question dominates: what does a compromised or simply buggy server reach?
|
||||
</P>
|
||||
<UL>
|
||||
<li>
|
||||
<Strong>One server, one container.</Strong> Process-level separation is not enough
|
||||
when different trust domains share a host. Each server should run in its own container
|
||||
with its own secrets, so a bug in one cannot read another's environment.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>No host mounts, no docker socket.</Strong> A tool-serving container has no
|
||||
business seeing the host filesystem or the container runtime.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Resource limits.</Strong> Memory and CPU caps per container turn a runaway
|
||||
loop into a restart instead of a host outage.
|
||||
</li>
|
||||
</UL>
|
||||
|
||||
<H2>4. Least-privilege tool design</H2>
|
||||
<P>
|
||||
The cheapest security control in MCP is deciding what tools exist at all. The AI will
|
||||
eventually call every tool you expose, with every argument shape it can think of.
|
||||
</P>
|
||||
<UL>
|
||||
<li>
|
||||
<Strong>Read-only by default.</Strong> Ship <code>search</code> and <code>get</code>{' '}
|
||||
tools first; add write tools only when the use case demands them, and separately.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Narrow arguments.</Strong> A <code>query_orders(customer_id)</code> tool is
|
||||
auditable; a <code>run_sql(sql)</code> tool is an incident report with a delay timer.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Validate inputs inside the tool.</Strong> Tool schemas are hints to the model,
|
||||
not enforcement. Your handler must validate as if the input came from the public
|
||||
internet — because via the model, it did.
|
||||
</li>
|
||||
</UL>
|
||||
|
||||
<H2>5. Logging without leaking</H2>
|
||||
<UL>
|
||||
<li>
|
||||
Log <Strong>that</Strong> a tool was called, its latency and result status — the
|
||||
metrics you need for operations.
|
||||
</li>
|
||||
<li>
|
||||
Be deliberate about logging <Strong>arguments and results</Strong>: they routinely
|
||||
contain customer data and occasionally contain secrets a model pasted into a query.
|
||||
Redact or hash by default.
|
||||
</li>
|
||||
<li>
|
||||
Keep an <Strong>audit trail</Strong> of who connected which client and when — OAuth
|
||||
gives you the identity for free; keep it attached to the call records.
|
||||
</li>
|
||||
</UL>
|
||||
|
||||
<H2>6. The prompt-injection surface</H2>
|
||||
<P>
|
||||
Two MCP-specific angles most checklists miss. First:{' '}
|
||||
<Strong>tool descriptions are instructions to the model</Strong>. If you install a
|
||||
third-party MCP server, its tool descriptions enter your AI's context — a malicious
|
||||
description can tell the model to exfiltrate data through another tool's arguments.
|
||||
Install servers the way you install browser extensions: from sources you trust, reading
|
||||
what they expose.
|
||||
</P>
|
||||
<P>
|
||||
Second: <Strong>tool results are untrusted input to the model.</Strong> If your tool
|
||||
returns content from the outside world (web pages, tickets, emails), that content can
|
||||
contain instructions the model may follow. You cannot fully solve this server-side, but
|
||||
you can avoid amplifying it: return structured data instead of raw HTML where possible,
|
||||
and never grant one server both broad read and broad write power — that pairing is what
|
||||
turns an injected instruction into an actual exfiltration.
|
||||
</P>
|
||||
|
||||
<H2>What a checklist can't fix</H2>
|
||||
<Note>
|
||||
Honesty section: no checklist makes an over-privileged design safe. If a server holds an
|
||||
admin API key and exposes a write-anything tool, perfect transport security just means
|
||||
the mistake is encrypted in flight. Prompt injection remains an open research problem —
|
||||
the mitigations above reduce blast radius, they do not eliminate the class. And a hosted
|
||||
platform (ours included) means trusting the platform's own isolation and encryption;
|
||||
read the vendor's{' '}
|
||||
<Link href="/security" className="text-[--color-accent] hover:underline">
|
||||
security page
|
||||
</Link>{' '}
|
||||
and hold them to specifics, not adjectives.
|
||||
</Note>
|
||||
|
||||
<H2>How BuildMyMCPServer maps to this list</H2>
|
||||
<P>
|
||||
For transparency about our own posture: generated servers run one-per-container behind an
|
||||
OAuth 2.1 authorization server; customer secrets are AES-256-GCM encrypted at rest and
|
||||
injected as environment variables at runtime, never logged or echoed back; and every
|
||||
server's TypeScript source can be exported for review. Templates carry the spec and code,
|
||||
never the author's credentials — you add your own on{' '}
|
||||
<Link href="/templates" className="text-[--color-accent] hover:underline">
|
||||
fork
|
||||
</Link>
|
||||
.
|
||||
</P>
|
||||
</ArticleShell>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
import { articleOgImage, OG_SIZE } from '@/lib/og-article';
|
||||
|
||||
export const runtime = 'edge';
|
||||
export const alt = 'MCP transports explained: stdio vs SSE vs Streamable HTTP';
|
||||
export const size = OG_SIZE;
|
||||
export const contentType = 'image/png';
|
||||
|
||||
export default function Image() {
|
||||
return articleOgImage({
|
||||
title: 'MCP transports explained: stdio vs SSE vs Streamable HTTP',
|
||||
tag: 'Explainer',
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,206 @@
|
||||
import { JsonLd } from '@/components/json-ld';
|
||||
import { StaticCodeBlock } from '@/components/static-code-block';
|
||||
import { articleJsonLd, breadcrumbJsonLd, faqJsonLd, pageMetadata } from '@/lib/seo';
|
||||
import Link from 'next/link';
|
||||
import { ArticleShell, H2, Note, P, Strong, Table, UL } from '../article-shell';
|
||||
|
||||
const PATH = '/guides/mcp-transports-explained';
|
||||
const TITLE = 'MCP transports explained: stdio vs SSE vs Streamable HTTP';
|
||||
const DESCRIPTION =
|
||||
'What each MCP transport actually is, why the spec deprecated HTTP+SSE in favor of Streamable HTTP, and which transport to pick for local tools, remote servers and serverless deployments.';
|
||||
|
||||
export const metadata = pageMetadata({ title: TITLE, description: DESCRIPTION, path: PATH });
|
||||
|
||||
const TRANSPORT_FAQ = [
|
||||
{
|
||||
q: 'Is SSE deprecated in MCP?',
|
||||
a: 'Yes. The HTTP+SSE transport from protocol version 2024-11-05 was replaced by Streamable HTTP in spec revision 2025-03-26. Servers may keep SSE endpoints for backward compatibility, but client support is degrading and platforms have been removing it through 2026.',
|
||||
},
|
||||
{
|
||||
q: 'What is the difference between Streamable HTTP and SSE in MCP?',
|
||||
a: 'The old HTTP+SSE transport needed two endpoints — a long-lived SSE stream for server-to-client messages and a separate POST endpoint for client-to-server messages. Streamable HTTP collapses this into one MCP endpoint that accepts HTTP POST and can optionally upgrade a response to an SSE stream when the server needs to push multiple messages.',
|
||||
},
|
||||
{
|
||||
q: 'When should I use stdio instead of HTTP?',
|
||||
a: 'Use stdio when the server runs on the same machine as the client — local dev tools, filesystem access, anything personal. The client spawns the server as a subprocess; there is no network surface and no auth to build. The moment more than one person or machine needs the server, you need Streamable HTTP.',
|
||||
},
|
||||
{
|
||||
q: 'Does Streamable HTTP require SSE?',
|
||||
a: 'No. SSE is optional within Streamable HTTP. A server can answer every request with a plain JSON response and never open a stream. Streams are only needed when the server wants to send progress notifications or multiple messages for one request.',
|
||||
},
|
||||
];
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<JsonLd
|
||||
data={articleJsonLd({
|
||||
title: TITLE,
|
||||
description: DESCRIPTION,
|
||||
path: PATH,
|
||||
datePublished: '2026-07-08',
|
||||
authorName: 'Marco Sadjadi',
|
||||
wordCount: 1350,
|
||||
})}
|
||||
/>
|
||||
<JsonLd
|
||||
data={breadcrumbJsonLd([
|
||||
{ name: 'Home', path: '/' },
|
||||
{ name: 'Guides', path: '/guides' },
|
||||
{ name: TITLE, path: PATH },
|
||||
])}
|
||||
/>
|
||||
<JsonLd data={faqJsonLd(TRANSPORT_FAQ)} />
|
||||
<ArticleShell
|
||||
title={TITLE}
|
||||
subtitle="MCP has shipped three transports in under two years. Two are current, one is on its way out. Here is what each one actually does, why the spec moved, and how to choose."
|
||||
updated="July 2026"
|
||||
>
|
||||
<H2>The three transports at a glance</H2>
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Transport</th>
|
||||
<th>Status</th>
|
||||
<th>Endpoints</th>
|
||||
<th>Best for</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>stdio</td>
|
||||
<td>Current</td>
|
||||
<td>None — subprocess pipes</td>
|
||||
<td>Local, single-user tools</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>HTTP+SSE</td>
|
||||
<td>Deprecated (2025-03-26)</td>
|
||||
<td>Two: SSE stream + POST</td>
|
||||
<td>Legacy remote servers only</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Streamable HTTP</td>
|
||||
<td>Current standard for remote</td>
|
||||
<td>One MCP endpoint (POST/GET)</td>
|
||||
<td>Every remote deployment</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
|
||||
<H2>stdio: the local transport</H2>
|
||||
<P>
|
||||
With <Strong>stdio</Strong>, the MCP client launches your server as a subprocess and
|
||||
exchanges JSON-RPC messages over stdin and stdout. There is no port, no TLS, no
|
||||
authentication — the security boundary is your operating system's process model. That is
|
||||
exactly right for tools that live on your own machine: filesystem helpers, local database
|
||||
access, developer utilities.
|
||||
</P>
|
||||
<P>
|
||||
The limitation is structural. A stdio server is bound to one machine and one client
|
||||
process. You cannot share it with a teammate, install it on a phone, or point ChatGPT's
|
||||
web app at it. It also means every user has to install a runtime (Node, Python, Docker)
|
||||
and keep the server updated themselves.
|
||||
</P>
|
||||
|
||||
<H2>HTTP+SSE: the deprecated remote transport</H2>
|
||||
<P>
|
||||
The first remote transport (protocol version 2024-11-05) paired two endpoints: the client
|
||||
opened a long-lived <Strong>Server-Sent Events</Strong> stream to receive messages, and
|
||||
sent its own messages to a separate HTTP POST endpoint the server advertised over that
|
||||
stream.
|
||||
</P>
|
||||
<P>This design turned out to be hostile to real infrastructure:</P>
|
||||
<UL>
|
||||
<li>
|
||||
<Strong>Load balancers</Strong> had to pin the SSE stream and the POST endpoint to the
|
||||
same backend instance, defeating horizontal scaling.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Serverless platforms</Strong> bill and time out on connection duration; a
|
||||
permanently open SSE stream is the pathological case.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Proxies and firewalls</Strong> routinely buffer or kill long-lived streams,
|
||||
producing connections that look healthy but deliver nothing.
|
||||
</li>
|
||||
</UL>
|
||||
<P>
|
||||
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.
|
||||
</P>
|
||||
|
||||
<H2>Streamable HTTP: the current standard</H2>
|
||||
<P>
|
||||
<Strong>Streamable HTTP</Strong> collapses everything onto a single MCP endpoint. The
|
||||
client sends JSON-RPC messages as ordinary HTTP POST requests. For a simple request, the
|
||||
server answers with a plain JSON response — request in, response out, connection closed.
|
||||
When the server needs to push several messages for one request (progress updates,
|
||||
notifications), it can upgrade that specific response to an SSE stream. Streaming becomes
|
||||
an option per response, not a mandatory architecture.
|
||||
</P>
|
||||
<UL>
|
||||
<li>Stateless requests by default — load balancers and serverless platforms just work.</li>
|
||||
<li>One URL to configure, secure and monitor instead of two coupled endpoints.</li>
|
||||
<li>
|
||||
Sessions are explicit (an <code>Mcp-Session-Id</code> header) instead of implied by a
|
||||
held-open socket, so a server can resume or reject them deliberately.
|
||||
</li>
|
||||
<li>Standard HTTP auth applies — which is what makes OAuth 2.1 integration clean.</li>
|
||||
</UL>
|
||||
<P>A remote server entry in a client config is now just a URL:</P>
|
||||
<StaticCodeBlock
|
||||
label="claude_desktop_config.json"
|
||||
code={`{
|
||||
"mcpServers": {
|
||||
"my-tool": {
|
||||
"url": "https://my-tool.mcp.buildmymcpserver.com/mcp",
|
||||
"auth": "oauth2"
|
||||
}
|
||||
}
|
||||
}`}
|
||||
/>
|
||||
|
||||
<H2>Choosing a transport</H2>
|
||||
<UL>
|
||||
<li>
|
||||
<Strong>Only you, on your machine</Strong> → stdio. Zero infrastructure, strongest
|
||||
isolation.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Anyone else, any other machine, any hosted client</Strong> → Streamable HTTP
|
||||
over TLS, with OAuth 2.1 in front of it. This is the only current answer for servers
|
||||
that Claude Desktop, Cursor and ChatGPT install over the internet.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Existing HTTP+SSE server</Strong> → migrate. The usual path is to serve the new
|
||||
single MCP endpoint alongside the legacy pair during a transition window, then drop the
|
||||
legacy endpoints once your clients are confirmed on the new transport.
|
||||
</li>
|
||||
</UL>
|
||||
<Note>
|
||||
Transport is only half of a production remote server — the other half is authorization.
|
||||
OAuth 2.1 with PKCE, Dynamic Client Registration and Resource Indicators is what makes a
|
||||
Streamable HTTP server actually installable from real clients. That's covered in{' '}
|
||||
<Link
|
||||
href="/guides/host-mcp-server-with-oauth"
|
||||
className="text-[--color-accent] hover:underline"
|
||||
>
|
||||
how to host a remote MCP server with OAuth
|
||||
</Link>
|
||||
.
|
||||
</Note>
|
||||
|
||||
<H2>FAQ</H2>
|
||||
{TRANSPORT_FAQ.map((f) => (
|
||||
<div key={f.q} className="mt-5">
|
||||
<h3 className="text-[15px] font-semibold tracking-tight text-[--color-fg]">{f.q}</h3>
|
||||
<p className="mt-1.5 text-[14px] leading-relaxed text-[--color-fg-muted]">{f.a}</p>
|
||||
</div>
|
||||
))}
|
||||
</ArticleShell>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
import { articleOgImage, OG_SIZE } from '@/lib/og-article';
|
||||
|
||||
export const runtime = 'edge';
|
||||
export const alt = 'Wrap any REST API as an MCP server';
|
||||
export const size = OG_SIZE;
|
||||
export const contentType = 'image/png';
|
||||
|
||||
export default function Image() {
|
||||
return articleOgImage({
|
||||
title: 'Wrap any REST API as an MCP server',
|
||||
tag: 'Guide',
|
||||
});
|
||||
}
|
||||
179
apps/web/app/(marketing)/guides/rest-api-to-mcp-server/page.tsx
Normal file
179
apps/web/app/(marketing)/guides/rest-api-to-mcp-server/page.tsx
Normal file
@ -0,0 +1,179 @@
|
||||
import { JsonLd } from '@/components/json-ld';
|
||||
import { StaticCodeBlock } from '@/components/static-code-block';
|
||||
import { articleJsonLd, breadcrumbJsonLd, pageMetadata } from '@/lib/seo';
|
||||
import Link from 'next/link';
|
||||
import { ArticleShell, H2, Note, P, Strong, Table, UL } from '../article-shell';
|
||||
|
||||
const PATH = '/guides/rest-api-to-mcp-server';
|
||||
const TITLE = 'Wrap any REST API as an MCP server';
|
||||
const DESCRIPTION =
|
||||
'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.';
|
||||
|
||||
export const metadata = pageMetadata({ title: TITLE, description: DESCRIPTION, path: PATH });
|
||||
|
||||
const BAD_PROMPT = `Create an MCP server for our API.
|
||||
Endpoints: GET /users, GET /users/:id, POST /users, PUT /users/:id,
|
||||
DELETE /users/:id, GET /orders, GET /orders/:id, POST /orders,
|
||||
GET /invoices, GET /invoices/:id, POST /invoices/:id/send ...`;
|
||||
|
||||
const GOOD_PROMPT = `Create an MCP server for our billing API (https://api.example.com).
|
||||
Tools:
|
||||
- find_customer: search customers by name or email, return id, plan, status
|
||||
- get_open_invoices: list unpaid invoices for a customer id, with amounts
|
||||
- send_invoice_reminder: send the dunning email for one invoice id
|
||||
Auth: BILLING_API_KEY sent as "Authorization: Bearer" header.
|
||||
Read-heavy; send_invoice_reminder is the only write action.`;
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<JsonLd
|
||||
data={articleJsonLd({
|
||||
title: TITLE,
|
||||
description: DESCRIPTION,
|
||||
path: PATH,
|
||||
datePublished: '2026-07-08',
|
||||
authorName: 'Marco Sadjadi',
|
||||
wordCount: 1400,
|
||||
})}
|
||||
/>
|
||||
<JsonLd
|
||||
data={breadcrumbJsonLd([
|
||||
{ name: 'Home', path: '/' },
|
||||
{ name: 'Guides', path: '/guides' },
|
||||
{ name: TITLE, path: PATH },
|
||||
])}
|
||||
/>
|
||||
<ArticleShell
|
||||
title={TITLE}
|
||||
subtitle="Every REST API is one wrapper away from being usable by Claude, Cursor or ChatGPT. The wrapper is an MCP server — and the difference between a useful one and a frustrating one is decided before any code exists, in how you design the tool surface."
|
||||
updated="July 2026"
|
||||
>
|
||||
<H2>The core mistake: 1:1 endpoint mapping</H2>
|
||||
<P>
|
||||
The obvious approach — one MCP tool per REST endpoint — produces a bad server. An AI
|
||||
assistant choosing between 30 near-identical tools burns context on the choice, chains
|
||||
three calls where one would do, and picks wrong often enough to erode trust. The
|
||||
assistant is not a REST client; it does not want your resource model, it wants{' '}
|
||||
<Strong>tasks</Strong>.
|
||||
</P>
|
||||
<StaticCodeBlock code={BAD_PROMPT} label="what not to do" />
|
||||
<P>
|
||||
Design the tool surface the way you would design CLI commands for a colleague:
|
||||
task-shaped, few, with the joins already done.
|
||||
</P>
|
||||
<StaticCodeBlock code={GOOD_PROMPT} label="the same API, task-shaped" />
|
||||
<UL>
|
||||
<li>
|
||||
<Strong>3–7 tools</Strong> is the sweet spot for a single-purpose server. More than ten
|
||||
and selection quality drops.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Fold lookups into the tool.</Strong> If sending a reminder needs a customer id,
|
||||
let <Strong>find_customer</Strong> exist — but do not expose the four intermediate
|
||||
endpoints the API needs internally.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Return shaped results, not raw payloads.</Strong> “id, plan, status” beats the
|
||||
full 80-field customer object; the model reads every byte you return.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Separate reads from writes</Strong> and say so in the description — clients
|
||||
like ChatGPT restrict write tools on individual plans (
|
||||
<Link href="/guides/chatgpt-mcp-connector" className="text-[--color-accent] hover:underline">
|
||||
details here
|
||||
</Link>
|
||||
), and a clean read/write split keeps the read tools usable everywhere.
|
||||
</li>
|
||||
</UL>
|
||||
|
||||
<H2>Auth: the API key never goes in the prompt</H2>
|
||||
<P>
|
||||
The wrapper needs your API's credential, and there is exactly one right place for it: an{' '}
|
||||
<Strong>encrypted secret</Strong>, referenced by name. In the prompt above,{' '}
|
||||
<Strong>BILLING_API_KEY</Strong> is a name, not a value. You provide the value separately
|
||||
in the dashboard; it is encrypted with AES-256-GCM at rest and injected into the server's
|
||||
container as an environment variable at runtime — never logged, never echoed back, never
|
||||
part of the generated source.
|
||||
</P>
|
||||
<P>
|
||||
The second auth layer is between the AI client and your MCP server: every deployed server
|
||||
sits behind OAuth 2.1 (PKCE, Dynamic Client Registration, Resource Indicators), so your
|
||||
wrapped API is not one guessable URL away from the public internet. How that handshake
|
||||
works:{' '}
|
||||
<Link href="/docs/oauth" className="text-[--color-accent] hover:underline">
|
||||
OAuth docs
|
||||
</Link>
|
||||
.
|
||||
</P>
|
||||
<Note>
|
||||
Start read-only. A read-only wrapper cannot damage anything while you learn how the
|
||||
assistant actually uses the tools; add the one or two write actions after a week of
|
||||
watching the call logs.
|
||||
</Note>
|
||||
|
||||
<H2>Rate limits: yours and theirs</H2>
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Layer</th>
|
||||
<th>What limits it</th>
|
||||
<th>What to do</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Client → MCP server</td>
|
||||
<td>OAuth gate before your container; plan quotas (free tier: 100k calls/mo)</td>
|
||||
<td>Nothing — enforced for you.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MCP server → upstream API</td>
|
||||
<td>The upstream's own rate limits</td>
|
||||
<td>
|
||||
Tell the generator: “respect a limit of N req/s; on 429, back off and surface the
|
||||
error”. Shaped tools help here too — one task call instead of five endpoint calls.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Model behavior</td>
|
||||
<td>Assistants retry failed calls</td>
|
||||
<td>
|
||||
Return clear error messages (“rate limited, retry in 30s”) — models read them and
|
||||
actually wait.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
|
||||
<H2>From prompt to installed tool, end to end</H2>
|
||||
<P>
|
||||
With the prompt written, the rest is mechanical:{' '}
|
||||
<Link href="/guides/create-mcp-server-without-code" className="text-[--color-accent] hover:underline">
|
||||
generation, static checks, container build and deploy
|
||||
</Link>{' '}
|
||||
take 45–90 seconds, and the dashboard gives you install snippets for{' '}
|
||||
<Link href="/guides/claude-desktop-mcp-setup" className="text-[--color-accent] hover:underline">
|
||||
Claude Desktop
|
||||
</Link>
|
||||
, Cursor and ChatGPT. The generated TypeScript is exportable — if your wrapper outgrows
|
||||
prompt-editing (complex retries, multi-step workflows), take the source and continue by
|
||||
hand with the boilerplate already written.
|
||||
</P>
|
||||
<P>
|
||||
If your API resembles something common — Notion, GitHub, Stripe, PostgreSQL — check{' '}
|
||||
<Link href="/templates" className="text-[--color-accent] hover:underline">
|
||||
the templates
|
||||
</Link>{' '}
|
||||
first: forking a working server and swapping in your credential is faster than writing
|
||||
any prompt. Otherwise, the{' '}
|
||||
<Link href="/pricing" className="text-[--color-accent] hover:underline">
|
||||
free tier
|
||||
</Link>{' '}
|
||||
covers one server — enough to wrap the API you use most and find out what your assistant
|
||||
does with it.
|
||||
</P>
|
||||
</ArticleShell>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
import { articleOgImage, OG_SIZE } from '@/lib/og-article';
|
||||
|
||||
export const runtime = 'edge';
|
||||
export const alt = 'Smithery alternative: when you need hosting, not a directory';
|
||||
export const size = OG_SIZE;
|
||||
export const contentType = 'image/png';
|
||||
|
||||
export default function Image() {
|
||||
return articleOgImage({
|
||||
title: 'Smithery alternative: when you need hosting, not a directory',
|
||||
tag: 'Alternative',
|
||||
});
|
||||
}
|
||||
196
apps/web/app/(marketing)/guides/smithery-alternative/page.tsx
Normal file
196
apps/web/app/(marketing)/guides/smithery-alternative/page.tsx
Normal file
@ -0,0 +1,196 @@
|
||||
import { JsonLd } from '@/components/json-ld';
|
||||
import { articleJsonLd, breadcrumbJsonLd, pageMetadata } from '@/lib/seo';
|
||||
import Link from 'next/link';
|
||||
import { ArticleShell, H2, Note, P, Strong, Table, UL } from '../article-shell';
|
||||
|
||||
const PATH = '/guides/smithery-alternative';
|
||||
const TITLE = 'Smithery alternative: when you need hosting, not a directory';
|
||||
const DESCRIPTION =
|
||||
'Smithery is the best-known MCP registry — thousands of servers, CLI install, hosting for listed servers. The gap: it assumes the server exists. Here is the route when it does not, and where Smithery clearly wins.';
|
||||
|
||||
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-07-08',
|
||||
authorName: 'Marco Sadjadi',
|
||||
wordCount: 1150,
|
||||
})}
|
||||
/>
|
||||
<JsonLd
|
||||
data={breadcrumbJsonLd([
|
||||
{ name: 'Home', path: '/' },
|
||||
{ name: 'Guides', path: '/guides' },
|
||||
{ name: TITLE, path: PATH },
|
||||
])}
|
||||
/>
|
||||
<ArticleShell
|
||||
title={TITLE}
|
||||
subtitle="Registry and generator solve different halves of the same problem. Smithery answers 'where do I find or publish an MCP server?' A generator answers 'who builds and hosts mine?' Picking the wrong one wastes an afternoon; here is how to tell them apart in two minutes."
|
||||
updated="July 2026"
|
||||
>
|
||||
<H2>What Smithery does well</H2>
|
||||
<P>
|
||||
Smithery is the closest thing MCP has to a package index. As of mid-2026 it lists{' '}
|
||||
<Strong>thousands of community-built servers</Strong> (their catalog crossed 6,000+ some
|
||||
time ago), searchable by category, installable via CLI, and — for servers published there
|
||||
— runnable as hosted remote endpoints with OAuth handled by the platform. Its Toolbox
|
||||
meta-server can even route an agent dynamically across registry servers so you don't
|
||||
wire each one by hand. Browsing and publishing are free; hosted execution and higher
|
||||
usage sit behind paid tiers.
|
||||
</P>
|
||||
<P>
|
||||
Two jobs it does better than anyone: <Strong>discovery</Strong> ("does a server for X
|
||||
already exist?") and <Strong>distribution</Strong> ("let people find and run the
|
||||
server I wrote"). If either is your actual need, stop reading and use Smithery.
|
||||
</P>
|
||||
|
||||
<H2>The assumption baked into a registry</H2>
|
||||
<P>
|
||||
Every path through Smithery starts from an existing server: one you found in the catalog,
|
||||
or one you wrote and published. The moment your need is a tool that{' '}
|
||||
<em>nobody has built</em> — a wrapper around your internal API, a scoped read-only view of
|
||||
your own database, a workflow specific to your team — the registry has nothing to list.
|
||||
You are back to the MCP SDK, TypeScript, container images and OAuth wiring before
|
||||
Smithery can help you host or distribute anything.
|
||||
</P>
|
||||
|
||||
<H2>The alternative: generate, then host</H2>
|
||||
<P>
|
||||
<Strong>BuildMyMCPServer</Strong> replaces the "write it first" step. Describe
|
||||
the tool in natural language; the platform generates the TypeScript MCP server, runs
|
||||
static checks, builds an isolated Docker container and deploys it behind a full OAuth 2.1
|
||||
authorization server (PKCE, Dynamic Client Registration, Resource Indicators) at a public
|
||||
Streamable HTTP URL. Claude Desktop, Cursor and ChatGPT connect with a copy-paste
|
||||
snippet. The full source stays exportable — if you later want to publish the server on
|
||||
Smithery or self-host it, you can take the code and go.
|
||||
</P>
|
||||
|
||||
<H2>Side by side</H2>
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>Smithery</th>
|
||||
<th>BuildMyMCPServer</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<Strong>Core job</Strong>
|
||||
</td>
|
||||
<td>Find, publish and run existing servers</td>
|
||||
<td>Create and host a server that doesn't exist yet</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Strong>Starting point</Strong>
|
||||
</td>
|
||||
<td>A server (yours or the catalog's)</td>
|
||||
<td>A sentence describing the tool</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Strong>Catalog size</Strong>
|
||||
</td>
|
||||
<td>Thousands of community servers</td>
|
||||
<td>Small first-party template gallery</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Strong>Hosting & auth</Strong>
|
||||
</td>
|
||||
<td>Hosted endpoints with OAuth for listed servers</td>
|
||||
<td>Every server deployed behind OAuth 2.1, isolated container</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Strong>Pricing shape</Strong>
|
||||
</td>
|
||||
<td>Free registry; paid hosting/usage tiers</td>
|
||||
<td>Free: 1 server, 100k calls/mo; Pro €49/mo</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<Strong>Exit path</Strong>
|
||||
</td>
|
||||
<td>Your code was always yours</td>
|
||||
<td>Full TypeScript source export</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</Table>
|
||||
|
||||
<Note>
|
||||
Smithery details are as of mid-2026 from public materials; check their site for current
|
||||
catalog size and tier pricing.
|
||||
</Note>
|
||||
|
||||
<H2>Decision rule</H2>
|
||||
<UL>
|
||||
<li>
|
||||
<Strong>The tool might already exist</Strong> → search Smithery first. Genuinely — five
|
||||
minutes there can save the whole build.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>You wrote a server and want users</Strong> → publish on Smithery; that is its
|
||||
home turf.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>The tool is bespoke to your company</Strong> → generate it. A registry cannot
|
||||
list what only you need.
|
||||
</li>
|
||||
<li>
|
||||
<Strong>Long-term:</Strong> the routes compose — generate the bespoke server, export
|
||||
the source, publish it wherever distribution helps.
|
||||
</li>
|
||||
</UL>
|
||||
|
||||
<H2>Honest caveats</H2>
|
||||
<P>
|
||||
We are the young product in this comparison: no SOC 2, a small template gallery next to a
|
||||
registry of thousands, and a generator that is bounded by what a prompt can specify —
|
||||
clear inputs, outputs and API calls. What we ask you to test is the part that matters
|
||||
when nothing in any catalog fits: prompt to hosted, OAuth-protected server in about a
|
||||
minute, on the{' '}
|
||||
<Link href="/pricing" className="text-[--color-accent] hover:underline">
|
||||
free tier
|
||||
</Link>
|
||||
, starting from scratch or from a{' '}
|
||||
<Link href="/templates" className="text-[--color-accent] hover:underline">
|
||||
template
|
||||
</Link>
|
||||
.
|
||||
</P>
|
||||
|
||||
<P>
|
||||
Related:{' '}
|
||||
<Link
|
||||
href="/guides/mcp-server-hosting-pricing"
|
||||
className="text-[--color-accent] hover:underline"
|
||||
>
|
||||
MCP hosting pricing compared
|
||||
</Link>{' '}
|
||||
·{' '}
|
||||
<Link
|
||||
href="/guides/hosted-mcp-platforms-compared"
|
||||
className="text-[--color-accent] hover:underline"
|
||||
>
|
||||
registries vs connectors vs infra vs generators
|
||||
</Link>{' '}
|
||||
·{' '}
|
||||
<Link href="/guides/composio-alternative" className="text-[--color-accent] hover:underline">
|
||||
Composio alternative
|
||||
</Link>
|
||||
.
|
||||
</P>
|
||||
</ArticleShell>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -15,6 +15,94 @@ export interface Article {
|
||||
}
|
||||
|
||||
export const ARTICLES: Article[] = [
|
||||
{
|
||||
slug: 'create-mcp-server-without-code',
|
||||
title: 'How to create an MCP server without writing code (2026)',
|
||||
description:
|
||||
'Turn a plain-language description into a hosted, OAuth-protected MCP server — no SDK, no Docker, no TypeScript. What works, what the limits are, and when you still need code.',
|
||||
tag: 'Guide',
|
||||
datePublished: '2026-07-08',
|
||||
},
|
||||
{
|
||||
slug: 'claude-desktop-mcp-setup',
|
||||
title: 'Connect a custom MCP server to Claude Desktop (step by step)',
|
||||
description:
|
||||
'How to add a remote MCP server to Claude Desktop: the config snippet, the OAuth flow on first use, and a troubleshooting table for 401s, missing servers and invisible tools.',
|
||||
tag: 'Setup',
|
||||
datePublished: '2026-07-08',
|
||||
},
|
||||
{
|
||||
slug: 'chatgpt-mcp-connector',
|
||||
title: 'Add a custom MCP connector to ChatGPT (2026 guide)',
|
||||
description:
|
||||
'How to connect a custom MCP server to ChatGPT: setup flow, the HTTPS and OAuth requirements, and the plan limits nobody mentions — write-capable connectors need a Business, Enterprise or Edu workspace.',
|
||||
tag: 'Setup',
|
||||
datePublished: '2026-07-08',
|
||||
},
|
||||
{
|
||||
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.',
|
||||
tag: 'Guide',
|
||||
datePublished: '2026-07-08',
|
||||
},
|
||||
{
|
||||
slug: 'mcp-server-hosting-pricing',
|
||||
title: 'MCP server hosting: pricing & options compared (2026)',
|
||||
description:
|
||||
'What hosting a remote MCP server actually costs in 2026 — Cloudflare Workers, Smithery, Composio, MintMCP and prompt-to-server generation, compared on price, effort and lock-in.',
|
||||
tag: 'Comparison',
|
||||
datePublished: '2026-07-08',
|
||||
},
|
||||
{
|
||||
slug: 'composio-alternative',
|
||||
title: 'Composio alternative for bespoke MCP tools',
|
||||
description:
|
||||
'Composio gives agents 1,000+ pre-built SaaS integrations. The gap is everything not in a catalog: your internal API, your database, your workflow. Here is the generate-your-own route — and where Composio clearly wins.',
|
||||
tag: 'Alternative',
|
||||
datePublished: '2026-07-08',
|
||||
},
|
||||
{
|
||||
slug: 'smithery-alternative',
|
||||
title: 'Smithery alternative: when you need hosting, not a directory',
|
||||
description:
|
||||
'Smithery is the best-known MCP registry — thousands of servers, CLI install, hosting for listed servers. The gap: it assumes the server exists. Here is the route when it does not, and where Smithery clearly wins.',
|
||||
tag: 'Alternative',
|
||||
datePublished: '2026-07-08',
|
||||
},
|
||||
{
|
||||
slug: 'mcp-transports-explained',
|
||||
title: 'MCP transports explained: stdio vs SSE vs Streamable HTTP',
|
||||
description:
|
||||
'What each MCP transport actually is, why the spec deprecated HTTP+SSE in favor of Streamable HTTP, and which transport to pick for local tools, remote servers and serverless deployments.',
|
||||
tag: 'Explainer',
|
||||
datePublished: '2026-07-08',
|
||||
},
|
||||
{
|
||||
slug: 'mcp-oauth-plain-english',
|
||||
title: 'OAuth 2.1 for MCP servers: PKCE, DCR and RFC 8707 in plain English',
|
||||
description:
|
||||
'The three RFCs behind MCP authorization — PKCE, Dynamic Client Registration and Resource Indicators — explained without jargon: what each one does, the full flow step by step, and what breaks when you skip one.',
|
||||
tag: 'Explainer',
|
||||
datePublished: '2026-07-08',
|
||||
},
|
||||
{
|
||||
slug: 'mcp-server-security-checklist',
|
||||
title: 'MCP server security checklist: secrets, isolation, auth',
|
||||
description:
|
||||
'A practical security checklist for production MCP servers: transport auth, secret storage and injection, container isolation, least-privilege tool design, safe logging, and the prompt-injection surface of tool descriptions.',
|
||||
tag: 'Checklist',
|
||||
datePublished: '2026-07-08',
|
||||
},
|
||||
{
|
||||
slug: 'mcp-server-ohne-code-erstellen',
|
||||
title: 'MCP Server ohne Code erstellen und hosten (2026)',
|
||||
description:
|
||||
'Wie Sie ohne Programmierkenntnisse einen eigenen MCP Server erstellen und hosten: Tool auf Deutsch oder Englisch beschreiben, generierten TypeScript-Server mit OAuth 2.1 deployen, Install-Snippet in Claude, Cursor oder ChatGPT einfügen.',
|
||||
tag: 'Anleitung',
|
||||
datePublished: '2026-07-08',
|
||||
},
|
||||
{
|
||||
slug: 'host-mcp-server-with-oauth',
|
||||
title: 'How to host a remote MCP server with OAuth (2026)',
|
||||
|
||||
@ -196,6 +196,8 @@ export function articleJsonLd(opts: {
|
||||
/** Named human author. Falls back to the Organization. */
|
||||
authorName?: string;
|
||||
wordCount?: number;
|
||||
/** BCP-47 language tag; the guides corpus is English except where noted. */
|
||||
inLanguage?: string;
|
||||
}): object {
|
||||
const image = opts.image ?? `${SITE_URL}${opts.path}/opengraph-image`;
|
||||
return {
|
||||
@ -208,7 +210,7 @@ export function articleJsonLd(opts: {
|
||||
image: image.startsWith('http') ? image : `${SITE_URL}${image}`,
|
||||
datePublished: opts.datePublished,
|
||||
dateModified: opts.dateModified ?? opts.datePublished,
|
||||
inLanguage: 'en',
|
||||
inLanguage: opts.inLanguage ?? 'en',
|
||||
author: opts.authorName
|
||||
? { '@type': 'Person', name: opts.authorName }
|
||||
: { '@id': `${SITE_URL}/#organization` },
|
||||
|
||||
Loading…
Reference in New Issue
Block a user