July 19, 2026 · 9 min read
The Model Context Protocol has official SDKs for TypeScript and Python. If you want Claude, Cursor, or any MCP client to call your Python code — a database query, an internal API, a calculation — building an MCP server is the right move. This guide walks through it from scratch.
A working MCP server in Python that exposes a tool. You'll be able to connect it to Claude Desktop or any stdio MCP client. The same pattern extends to any tool you want to expose — databases, APIs, file systems, internal data.
Python 3.10+. That's it. The MCP SDK is a pip install.
pip install mcpIf you're using uv (recommended for MCP servers since Claude Desktop uses it for subprocess management):
uv init my-mcp-server
cd my-mcp-server
uv add mcpCreate server.py:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("my-server")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers together."""
return a + b
@mcp.tool()
def greet(name: str) -> str:
"""Return a greeting for the given name."""
return f"Hello, {name}!"
if __name__ == "__main__":
mcp.run()That's a complete MCP server. FastMCP reads the function signatures and docstrings to generate the tool definitions automatically. The @mcp.tool() decorator registers each function as an MCP tool.
Run the server to confirm it starts without errors:
python server.pyYou should see it start in stdio mode. Use MCP Inspector to send test requests:
npx @modelcontextprotocol/inspector python server.pyMCP Inspector opens a local UI where you can call your tools manually and see the results — the fastest way to confirm your server works before connecting it to a real client.
Add the server to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"my-server": {
"command": "python",
"args": ["/absolute/path/to/server.py"]
}
}
}If you're using uv:
{
"mcpServers": {
"my-server": {
"command": "uv",
"args": ["run", "/absolute/path/to/server.py"]
}
}
}Restart Claude Desktop. You should see your server appear in the tools panel (the hammer icon). Claude can now call your tools.
Here's a more realistic example — a tool that queries a SQLite database:
import sqlite3
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("db-server")
DB_PATH = "/path/to/your/database.db"
@mcp.tool()
def query_db(sql: str) -> list[dict]:
"""Run a read-only SQL query and return results as a list of rows."""
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
cursor = conn.execute(sql)
rows = [dict(row) for row in cursor.fetchall()]
conn.close()
return rows
if __name__ == "__main__":
mcp.run()Now Claude can query your SQLite database in natural language. You'd ask "how many users signed up last week?" and Claude calls query_db with the appropriate SQL.
FastMCP uses type annotations to generate the JSON Schema for each tool's input. If you omit annotations, the generated schema will be weak and the AI may not call the tool correctly. Always annotate parameters:
# Good — fully annotated
@mcp.tool()
def search(query: str, limit: int = 10) -> list[str]:
"""Search indexed content."""
...
# Bad — unannotated, schema will be incomplete
@mcp.tool()
def search(query, limit=10):
...MCP also has Resources — static or dynamic data the client can read, like file contents or configuration. Add a resource with @mcp.resource():
@mcp.resource("config://app")
def get_config() -> str:
"""Return the current application config."""
return open("/path/to/config.yaml").read()Resources are read when the client requests them, not when the AI decides to call them. Think of tools as actions and resources as data the client subscribes to.
stdio works great for local tools. For a remotely-hosted server, switch to streamable HTTP:
if __name__ == "__main__":
mcp.run(transport="streamable-http", port=8080)Then connect clients via the HTTP URL instead of a subprocess command. This is how AgentReady runs — a hosted MCP server that any client can reach without installing anything locally.
Once you have a working server, the pattern for any tool is the same: write a Python function, annotate it, decorate with @mcp.tool(). Some useful directions:
— Wrap your internal REST APIs as tools so agents can call them without parsing JSON themselves.
— Index your documentation and expose a search tool (or use AgentReady for this so you don't have to build the RAG pipeline).
— Expose database queries with guardrails — only read operations, parameterized SQL to prevent injection.
The protocol is simple and the SDK handles the plumbing. The valuable part is deciding what tools your agents actually need.