The Model Context Protocol (MCP) is Anthropic’s open standard for connecting AI agents to external tools and data sources. It uses JSON-RPC over stdio or HTTP, giving any MCP-compatible client – Claude Desktop, Claude Code, Cursor, or your own agent – a way to discover and call tools on your server. Think of it as a USB-C port for AI: one protocol, many connections.
Here’s the fastest way to get an MCP server running:
| |
That’s a complete, working MCP server. Install the SDK with pip install mcp, run the script, and any MCP client can connect over stdio and call greet. The @mcp.tool() decorator handles all the protocol plumbing – it reads your function signature, type hints, and docstring to generate the JSON schema that clients need for tool discovery.
Setting Up Your Project
Start by creating a project directory and installing the MCP Python SDK. You need Python 3.10 or higher.
| |
The mcp package includes the FastMCP high-level server class and all the transport layers. We’re pulling in httpx because our example server will make async HTTP calls.
If you prefer uv (and you probably should for new projects), the setup is even cleaner:
| |
Building a Server with Tools
Tools are the most common MCP capability. They let an AI agent take actions – query a database, call an API, read a file. Each tool gets a name, a description (from the docstring), and a typed parameter schema (from the function signature).
Here’s a server that wraps a real API. This one queries the Open-Meteo weather API, which is free and requires no API key:
| |
A few things worth noting. The @mcp.tool() decorator must be called with parentheses – @mcp.tool, without them, throws a TypeError. Async functions work out of the box. The docstring becomes the tool description that the AI model reads, so write it like you’re explaining the tool to a colleague. The Args section in the docstring maps to parameter descriptions in the generated schema.
Exposing Data with Resources
Resources are MCP’s way of exposing read-only data. While tools perform actions, resources serve up content – config files, database records, status pages. Clients can list and read resources by URI.
| |
The curly-brace syntax in "users://{user_id}/profile" creates a resource template. The client can request users://alice/profile and MCP routes the alice value into your function’s user_id parameter. Static resources (no placeholders) are simpler – they return the same data every time.
Connecting Clients to Your Server
The most common way to test an MCP server is through Claude Desktop or Claude Code. Both use a JSON config file to know which servers to launch.
For Claude Desktop, edit ~/.config/Claude/claude_desktop_config.json (Linux) or ~/Library/Application Support/Claude/claude_desktop_config.json (macOS):
| |
If you used uv, point the command at uv instead:
| |
Restart Claude Desktop after editing the config. The server shows up in the connectors menu, and Claude can immediately call your tools.
You can also test your server programmatically with the MCP Python SDK’s client:
| |
This spins up your server as a subprocess, connects over stdio, and lets you call tools directly. It’s the fastest feedback loop for development.
Common Errors and Fixes
TypeError: FastMCP.tool() missing 1 required positional argument – You wrote @mcp.tool instead of @mcp.tool(). The decorator needs parentheses even with no arguments.
Server hangs or produces garbled output – You’re printing to stdout in a stdio-mode server. Any print() call corrupts the JSON-RPC message stream. Use print("debug info", file=sys.stderr) or the logging module instead, which defaults to stderr.
Claude Desktop doesn’t show the server – Check three things: (1) the path in your config JSON is absolute, not relative, (2) you restarted Claude Desktop fully (quit, not just close the window), (3) your Python/uv command is correct. Run which python to get the exact path if needed.
ModuleNotFoundError: No module named 'mcp' – The server launched with a different Python than the one where you installed mcp. Use the full path to the Python binary in your config: "command": "/home/you/project/.venv/bin/python".
Tool calls return empty or error responses – Your tool function is probably raising an unhandled exception. MCP catches it but the error message may not surface. Add a try/except in your tool function and return a descriptive error string instead of letting exceptions propagate silently.
httpx.ConnectError or timeout errors – Your async HTTP client can’t reach the external API. Check your network connection, and make sure you’re setting a reasonable timeout on requests. The Open-Meteo API used in our examples is free and doesn’t need authentication, but rate limits apply.
Related Guides
- How to Build a Multi-Agent Pipeline Using Anthropic’s Agent SDK and MCP
- How to Build Agents with LangGraph
- How to Add Human-in-the-Loop Approval to AI Agents
- How to Build a Retrieval Agent with Tool Calling and Reranking
- How to Build Multi-Agent Systems with CrewAI
- How to Build Autonomous Agents with the OpenAI Assistants API
- How to Build a GitHub Issue Triage Agent with LLMs and the GitHub API
- How to Test and Debug AI Agents with LangSmith Tracing
- How to Build a Data Pipeline Agent with LLMs and Pandas
- How to Build a Planning Agent with Task Decomposition