🔌

MCP Server Builder

Verified

by Anthropic

Helps you design and build MCP servers that expose external services as tools an LLM can call. Covers Python (FastMCP) and Node/TypeScript (official MCP SDK). Walks through tool schema design, error handling, transport selection (stdio vs SSE), and packaging for distribution.

mcpmodel-context-protocolintegrationpythontypescriptsdkanthropic
View on GitHub

MCP Server Builder

Guide users through building a high-quality MCP server. Adapted from Anthropic's official mcp-builder skill.

Decision tree

  1. 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.

  1. Transport: stdio or SSE?

- stdio → for local Claude Code integration (default). Simpler.

- SSE → for remote servers accessed over HTTP.

  1. 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.json with command + args.
  • For wider distribution: publish to PyPI (Python) or npm (Node). Include a clear README with the install + Claude config snippet.