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:
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user