MCP Server Builder
Guide users through building a high-quality MCP server. Adapted from Anthropic's official mcp-builder skill.
Decision tree
- Language: Python or Node?
- Python → use FastMCP. Best when wrapping a Python SDK or doing data work.
- Node/TypeScript → use the official MCP SDK. Best when wrapping a JS-only API or running in a JS runtime.
- Transport: stdio or SSE?
- stdio → for local Claude Code integration (default). Simpler.
- SSE → for remote servers accessed over HTTP.
- Auth model: none / API key / OAuth?
Python (FastMCP) skeleton
from mcp.server.fastmcp import FastMCP
import httpx
mcp = FastMCP("my-server")
@mcp.tool()
async def search(query: str, limit: int = 10) -> list[dict]:
"""Search the corpus and return matching items."""
async with httpx.AsyncClient() as client:
r = await client.get("https://api.example.com/search",
params={"q": query, "limit": limit})
r.raise_for_status()
return r.json()["results"]
if __name__ == "__main__":
mcp.run()
Install: pip install mcp httpx. Run: python server.py.
Node (TypeScript) skeleton
import { Server } from "@modelcontextprotocol/sdk/server/index.js"
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js"
const server = new Server({ name: "my-server", version: "0.1.0" }, { capabilities: { tools: {} } })
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: "search",
description: "Search the corpus",
inputSchema: { type: "object", properties: { query: { type: "string" } }, required: ["query"] },
}],
}))
server.setRequestHandler(CallToolRequestSchema, async (req) => {
if (req.params.name === "search") {
const r = await fetch(`https://api.example.com/search?q=${encodeURIComponent(req.params.arguments.query)}`)
return { content: [{ type: "text", text: await r.text() }] }
}
throw new Error("Unknown tool")
})
await server.connect(new StdioServerTransport())
Install: npm i @modelcontextprotocol/sdk. Run: tsx server.ts.
Tool design checklist
- One tool per logical user action — not one per API endpoint.
- Input schema: required vs optional clearly marked. Use enums for fixed sets.
- Return concise structured data. If the upstream returns 50 fields and the LLM only needs 5, project down.
- Errors: throw with a human-readable message. The LLM will read it and retry intelligently.
- Idempotency: mark side-effecting tools clearly in the description ("creates", "deletes").
Common mistakes to avoid
- One mega-tool that takes an "action" param. The schema becomes unparseable.
- Returning raw HTML or 100k-token blobs. Pre-process or paginate.
- Hard-coded credentials. Always read from env vars.
- No timeout on outbound calls. Always cap with
httpx.Timeout/AbortController.
Distribution
- For Claude Code: add to
~/.claude/mcp.jsonwithcommand+args. - For wider distribution: publish to PyPI (Python) or npm (Node). Include a clear README with the install + Claude config snippet.