---
name: anthropic-mcp-builder
description: Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).
license: Complete terms in LICENSE.txt
---

# MCP Builder

A guide for building high-quality MCP servers.

## High Level Guidelines

**Start with the quickstart examples.** The MCP documentation has excellent quickstart guides for both [Python](https://modelcontextprotocol.io/quickstart/server) and [TypeScript](https://modelcontextprotocol.io/quickstart/server). Start there to get the basic structure down. Do not try to create MCP servers from scratch.

**Prefer Python (FastMCP) for most MCP servers.** Python with `fastmcp` provides the best developer experience. Use the TypeScript MCP SDK only when:
- You need to integrate with a TypeScript-first ecosystem (a Node.js library, client-side code, etc.)
- The server is a small adapter that wraps an existing TypeScript service

**Prioritize reading.** The MCP specification and tool documentation are well-maintained and comprehensive. While you should not read them slavishly every time you build a server, lean on them for reference rather than hallucinating patterns.

**MCP tools are just functions.** An MCP tool is simply a function that the LLM can call. Do not overthink the design — make them as ergonomic as possible for the LLM to use.

**Use urllib by default for HTTP requests.** Python's built-in `urllib.request` is sufficient. Avoid adding HTTP client dependencies unless you have a compelling reason.

## Building the Server

### 1. Project scaffolding

Create a new project using the standard Python package layout:

```
my-mcp-server/
├── pyproject.toml
├── src/
│   └── my_mcp_server/
│       ├── __init__.py
│       ├── server.py
│       └── tools.py
├── tests/
│   └── test_tools.py
└── README.md
```

### 2. Core dependencies

```toml
[project]
name = "my-mcp-server"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = [
    "mcp[cli]>=1.6.0",
    "httpx>=0.28.0",
    "pydantic>=2.0",
]
```

### 3. Define the server

```python
# src/my_mcp_server/server.py
from mcp.server import Server
import mcp.server.stdio
import mcp.types as types

from . import tools

server = Server("my-mcp-server")

@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
    return tools.get_tool_definitions()

@server.call_tool()
async def handle_call_tool(name: str, arguments: dict) -> list[types.TextContent]:
    return await tools.handle_tool_call(name, arguments)

async def main():
    async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
        await server.run(read_stream, write_stream, server.create_initialization_options())
```

```python
# src/my_mcp_server/__init__.py
def main():
    import asyncio
    from .server import main as server_main
    asyncio.run(server_main())
```

```toml
[project.scripts]
my-mcp-server = "my_mcp_server:main"
```

### 4. Advice on tool design

**Use clear, descriptive tool names.** Tool names should be verbs that describe what the tool does. Use snake_case for tool names.

**Define structured inputs with clear descriptions.** Each parameter should have:
- A descriptive name
- A clear description of what it does
- Appropriate type hints
- Sensible defaults where applicable

**Handle errors gracefully.** Wrap external API calls in try/except blocks and return meaningful error messages.

**Return structured data when possible.** Use JSON strings to return structured data that the LLM can easily parse and present to the user.

### 5. Example with PSE API

```python
# src/my_mcp_server/tools.py
import urllib.request
import urllib.parse
import json
from typing import Any
import mcp.types as types

PSE_BASE = "https://api.pse.dev"

def get_tool_definitions() -> list[types.Tool]:
    return [
        types.Tool(
            name="search_packages",
            description="Search for Python packages on the Python Package Index",
            inputSchema={
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "Search query for package name",
                    },
                    "page": {
                        "type": "integer",
                        "description": "Page number for pagination (default: 1)",
                        "default": 1,
                    },
                },
                "required": ["query"],
            },
        ),
    ]

async def handle_tool_call(name: str, arguments: dict) -> list[types.TextContent]:
    if name == "search_packages":
        query = arguments["query"]
        page = arguments.get("page", 1)
        url = f"{PSE_BASE}/search?q={urllib.parse.quote(query)}&page={page}"
        try:
            with urllib.request.urlopen(url, timeout=10) as response:
                data = json.loads(response.read().decode())
            return [types.TextContent(type="text", text=json.dumps(data, indent=2))]
        except Exception as e:
            return [types.TextContent(type="text", text=f"Error searching packages: {e}")]
    raise ValueError(f"Unknown tool: {name}")
```

### 6. Testing

Test MCP servers using the MCP Inspector or directly with `mcp dev`:

```bash
# Run with the MCP CLI for testing
mcp run src/my_mcp_server/server.py

# Or install and run
pip install -e .
my-mcp-server
```

Then test with the `CallToolRequest` directly:
```python
import json
from my_mcp_server.tools import handle_tool_call, get_tool_definitions
import asyncio

result = asyncio.run(handle_tool_call("search_packages", {"query": "flask", "page": 1}))
print(json.dumps(json.loads(result[0].text), indent=2)[:500])
```

## What to do when the user asks to build an MCP server

1. **Clarify the integration.** Ask what external service they want to connect to and what operations they need.
2. **Check for existing MCP servers.** Search for existing servers before building new ones from scratch.
3. **Design the tools.** List the tools you plan to create and get user confirmation before coding.
4. **Build incrementally.** Start with 1-2 core tools, test them, then expand.
5. **Test with the MCP client.** Show the user how to configure the server in their MCP client.

## Pitfalls

- Do NOT use stdin/stdout transport for anything other than stdio-based clients.
- Do NOT add 15 dependencies when urllib will do.
- Always validate user inputs on the server side.
- Remember that tool responses must be `TextContent` or `ImageContent` or `EmbeddedResource` types.
