Model Context Protocol (MCP) is Anthropic’s open standard for giving LLMs access to external tools and data sources. Instead of hardcoding tool integrations into your app, you build an MCP server that exposes tools — and any MCP-compatible client (Claude Desktop, Cursor, custom agents) can use them without extra glue code.
This guide walks through building a working MCP server in Python from scratch, connecting it to Claude Desktop, and writing tools with proper input validation and error handling.
What You’re Building
An MCP server is a small process that runs locally (or remotely) and communicates with an LLM client over a standard protocol. The client discovers what tools your server exposes, the LLM decides when to call them, and your server executes the logic and returns results.
You define tools in Python using the FastMCP class from the official SDK. Type hints and docstrings automatically generate the JSON schema that the client uses to understand each tool.
Environment Setup
You need Python 3.10+ and uv (the fast Python package manager). If you don’t have uv yet:
| |
Create a new project directory and install dependencies:
| |
The mcp[cli] package includes FastMCP, the high-level wrapper, plus command-line tools for installing your server into Claude Desktop. Use SDK version 1.2.0 or higher — earlier versions lack several FastMCP features.
Your First Tool
Create server.py:
| |
FastMCP reads the function signature and docstring to generate the tool schema automatically. The Args: section in the docstring becomes the parameter descriptions in the schema — this is what the LLM sees when deciding how to call your tool.
Test it immediately using the MCP inspector:
| |
This starts the inspector at http://localhost:5173. You can call tools manually and see the JSON-RPC messages flowing, which is invaluable for debugging before you wire up a real client.
Connecting to Claude Desktop
Claude Desktop reads server configurations from a JSON file. Open (or create) the config file:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
Add your server:
| |
Replace /absolute/path/to/my-mcp-server with the actual path — run pwd in your project directory to get it. Relative paths don’t work here.
Restart Claude Desktop completely (Cmd+Q on macOS, not just close the window). Once restarted, click the attachment icon in the chat input. You should see your server listed under “Connectors.” If it doesn’t appear, check ~/Library/Logs/Claude/mcp-server-my-tools.log for errors.
Alternatively, let the MCP CLI handle the config for you:
| |
This writes the config entry automatically and is less error-prone.
Input Validation with Pydantic
For tools that accept complex inputs, use Pydantic models. FastMCP generates the full JSON schema including field descriptions and constraints:
| |
Pydantic validators run before your function body executes. If the LLM passes an invalid value — say max_results: 500 when the max is 100 — the validation error surfaces in the tool call result and the LLM can correct its input.
Async Tools and External API Calls
Tools can be async. Use this for anything that does I/O — HTTP requests, database queries, file reads:
| |
Return structured data as dicts or lists — FastMCP serializes them to JSON for the client. For errors, returning a dict with an "error" key is the pattern most clients expect. Raising exceptions works too but gives the LLM less context about what went wrong.
Connecting to Other MCP Clients
Claude Desktop is the easiest starting point, but MCP clients exist for other environments. The configuration format is consistent across them:
Cursor: Edit .cursor/mcp.json in your project root:
| |
Custom Python agent using the MCP client library:
| |
This is useful for integration tests or building agents that call your MCP server programmatically.
Debugging Tips
The server starts but no tools appear in Claude: Usually a path issue or syntax error in server.py. Run uv run server.py directly from your terminal — any Python errors will show immediately. Also check the MCP log file mentioned above.
“Command not found: uv”: Claude Desktop may not inherit your shell’s PATH. Use the full path to uv instead:
| |
Run which uv in your terminal to get the full path.
Tools fail silently: Check that you’re not writing to stdout anywhere in your server code. print() statements corrupt the STDIO transport. Use print("...", file=sys.stderr) or the logging module instead.
Pydantic validation errors: The MCP inspector (uv run mcp dev server.py) shows the full JSON-RPC exchange including error details. Run it, call the failing tool, and read the error message in the response.
What to Build Next
Once the basics work, useful directions include:
- Resources: Expose file contents or database records as readable resources (not just callable tools)
- Prompts: Pre-built prompt templates the LLM can request from your server
- SSE transport: Switch from stdio to HTTP+SSE for servers that multiple clients share simultaneously
- Authentication: Add API keys via environment variables in the Claude Desktop config’s
"env"section
The MCP ecosystem is moving fast — check the official Python SDK at github.com/modelcontextprotocol/python-sdk for new features. The core protocol is stable but FastMCP is still adding conveniences.
Related Guides
- How to Use Claude’s Model Context Protocol (MCP)
- How to Use the Weights and Biases Prompts API for LLM Tracing
- How to Run Fast LLM Inference with the Groq API
- How to Run Open-Source Models with the Replicate API
- How to Use the Cerebras API for Fast LLM Inference
- How to Use the OpenRouter API for Multi-Provider LLM Access
- How to Track ML Experiments with Weights and Biases
- How to Use the Anthropic Prompt Caching API with Context Blocks
- How to Use the Anthropic Tool Use API for Agentic Workflows
- How to Build AI Apps with the Vercel AI SDK and Next.js