Over the past few weeks, I built MCP servers for 9 APIs: CoinGecko, Stripe, Jira, PostHog, Plausible, Etherscan, DeFiLlama, Jobber, and Resend. 9 servers, 68 tools, all published to npm and indexed on Glama.
In doing so, I noticed the same architecture working again and again. If you’re building MCP servers for your own API — or considering hiring someone to — here’s what I learned.
The Three-Layer Architecture
Every MCP server I built contains three layers:
1. Tool Definitions (Contract Layer)
Each API endpoint maps to an MCP tool with a typed input schema. I use Zod for validation — it catches bad inputs before they reach your API.
{
name: "send_email",
description: "Send a single email via Resend",
inputSchema: {
from: z.string().email(),
to: z.union([z.string().email(), z.array(z.string().email())]),
subject: z.string().min(1),
html: z.string().optional(),
text: z.string().optional(),
},
}
The importance of description goes beyond what you’d expect. LLMs use these descriptions to decide which tool to call. Vague descriptions like “send email” waste tokens. Specific ones like “Send a single transactional email via Resend API. Supports HTML and plain text. Returns message ID and delivery status.” ensure the tool gets used correctly.
2. API Client (Plumbing Layer)
This layer handles authentication, rate limiting, and error translation. Key insight: Don’t leak HTTP errors to the LLM. Convert them into structured, actionable error messages.
// Bad: "Error: 429"
// Good: "Rate limited by Resend API. Retry after 30 seconds. You've sent 100 emails in the last hour."
3. Output Formatter (Presentation Layer)
Raw JSON dumps are terrible for LLMs. Format responses as Markdown tables, bullet points, or structured text. LLMs decide their next steps by reading output — make it scan-friendly.
## Email Sent Successfully
- **Message ID:** abc123
- **From:** [email protected]
- **To:** [email protected]
- **Subject:** Your weekly report
- **Status:** Queued for delivery
Patterns That Worked Repeatedly
Mock Mode
Every server includes a mock mode that returns realistic fake data. This lets developers test without real API credentials. It’s also how I tested during development — no API keys needed.
Progressive Disclosure
If the LLM only needs 3 tools, don’t expose all 18 at once. Group tools by use case. The Resend server has tools for emails, contacts, domains, and API keys — developers sending emails don’t need to see domain management tools.
Error Recovery
APIs fail, networks timeout, rate limits trigger. Servers should handle retries internally and tell the LLM how long to wait in error messages.
Best Practices for Tool Design
- Be specific with descriptions: Explain input format, constraints, and what comes back
- Use Zod for input validation: Catch errors before they reach the API
- Unify error formats: Let the LLM know how to handle failures
- Return readable output: Markdown tables beat raw JSON
- Include mock mode: Makes testing and development easier
Published MCP Servers
| Server | Tools | Platform |
|---|---|---|
| @friendlygeorge/mcp-resend | Email, contacts, domains, API keys | Resend |
| @friendlygeorge/mcp-stripe | Payments, subscriptions, customers | Stripe |
| @friendlygeorge/mcp-jira | Issues, projects, workflows | Jira |
| @friendlygeorge/mcp-coingecko | Crypto prices, market data | CoinGecko |
| @friendlygeorge/mcp-etherscan | Blockchain queries, contracts | Etherscan |
| @friendlygeorge/mcp-plausible | Analytics, visitors | Plausible |
| @friendlygeorge/mcp-posthog | Events, users, cohorts | PostHog |
| @friendlygeorge/mcp-defillama | DeFi protocols, TVL | DeFiLlama |
| @friendlygeorge/mcp-jobber | Jobs, clients, quotes | Jobber |
All servers are published to npm and indexed on Glama for developers to find and reference.
Source: https://dev.to/friendlygeorge/i-built-mcp-servers-for-9-saas-apis-heres-what-i-learned-about-the-pattern-2mf7
