The Model Context Protocol (MCP) has rapidly become the standard way to connect AI language models to external tools, files, databases, and APIs. If you are a Python developer looking to give AI agents access to your own systems, building an MCP server is the right approach.
In this tutorial, we will build a complete MCP server in Python that lets an AI agent read files, query a SQLite database, and call a web API — all through a standardized protocol that works with Claude, ChatGPT, and any MCP-compatible client.
What Is MCP?
MCP is an open protocol that defines a standard way for AI applications (clients) to discover and use tools provided by a server. Think of it like a USB-C port for AI: instead of writing custom integration code for each AI model, you build one MCP server and any compatible client can use it.
The protocol supports three types of capabilities:
- Tools — functions the AI can call with structured arguments (e.g.,
query_database(sql: str)) - Resources — data the AI can read (e.g., a file, a database table schema, API documentation)
- Prompts — reusable prompt templates the AI can fill in with context
Our server will implement all three.
Prerequisites
- Python 3.10 or later
- Basic familiarity with async Python (
asyncio)
Install the fastmcp package:
pip install fastmcp
FastMCP is the most popular Python MCP server framework. It provides a simple decorator-based API that handles the JSON-RPC protocol for you. Alternatively, you can use the official mcp package directly for lower-level control.
Building the Server
Here is our complete server. It exposes three tools, one resource, and one prompt template:
from fastmcp import FastMCP
import sqlite3
import aiohttp
import os
mcp = FastMCP(
name="DataConnector",
instructions="Connect AI agents to files, databases, and web APIs"
)
# --- Tool 1: Read a local file ---
@mcp.tool()
async def read_file(path: str) -> str:
"""Read the contents of a local file. Returns the full text content."""
if not os.path.exists(path):
return f"Error: File not found: {path}"
with open(path, "r") as f:
return f.read()
# --- Tool 2: Query a SQLite database ---
@mcp.tool()
async def query_database(sql: str) -> str:
"""Execute a read-only SQL query against a SQLite database and return results."""
# Safety: only allow SELECT
if not sql.strip().upper().startswith("SELECT"):
return "Error: Only SELECT queries are allowed"
try:
conn = sqlite3.connect("data.db")
cursor = conn.execute(sql)
rows = cursor.fetchall()
columns = [desc[0] for desc in cursor.description]
conn.close()
# Format as markdown table
header = "| " + " | ".join(columns) + " |"
separator = "| " + " | ".join("---" for _ in columns) + " |"
data_rows = ["| " + " | ".join(str(v) for v in row) + " |" for row in rows]
return "\n".join([header, separator] + data_rows)
except Exception as e:
return f"Error: {e}"
# --- Tool 3: Fetch JSON from a URL ---
@mcp.tool()
async def fetch_json(url: str) -> str:
"""Fetch JSON data from a URL and return it as formatted text."""
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
if resp.status != 200:
return f"Error: HTTP {resp.status}"
data = await resp.json()
import json
return json.dumps(data, indent=2)[:5000] # Limit response size
# --- Resource: API documentation ---
@mcp.resource("docs://api")
async def api_docs() -> str:
"""Read-only access to the API documentation."""
return """
Available Tools:
- read_file(path): Read a local file
- query_database(sql): Execute a SELECT query on data.db
- fetch_json(url): Fetch JSON from a URL
Safety Notes:
- Database queries are limited to SELECT only
- File reading has no write/delete access
- JSON fetches are limited to 5000 characters
"""
# --- Prompt template ---
@mcp.prompt()
async def analyze_data(question: str) -> str:
"""A prompt template for data analysis workflows."""
return f"""
You are a data analyst. The user has asked: {question}
Use the available tools to:
1. Check the database schema first
2. Run the appropriate queries
3. Summarize findings in plain language
"""
if __name__ == "__main__":
mcp.run()
How the AI Agent Uses These Tools
When an MCP client (like Claude Desktop, Cursor, or a custom agent) connects to this server, here is what happens:
-
Discovery: The client calls the server to list available tools, resources, and prompts. It sees
read_file,query_database,fetch_json, and thedocs://apiresource. -
Tool Selection: When the user asks “What’s in my data.db?”, the AI recognizes that
query_databaseis the appropriate tool. It constructs a SELECT query and sends it to the server. -
Execution: The server runs the query and returns the results as a formatted markdown table. The AI presents this to the user in natural language.
This loop repeats for as many tool calls as the conversation needs.
Adding MCP to Your Existing Projects
The pattern is straightforward:
- Identify the functions in your existing code that an AI agent would find useful
- Decorate them with
@mcp.tool()and add a docstring that describes what the tool does (the AI reads this docstring to decide when to call the tool) - Start the server and point your MCP client at it
For production deployments, you will want to add:
- Authentication — verify that the connecting client is authorized
- Rate limiting — prevent the AI from calling expensive tools in tight loops
- Input validation — especially for SQL queries and file paths, to prevent path traversal or injection attacks
- Logging — track which tools the AI called and with what arguments, for debugging and auditing
Connecting to MCP Clients
Once your server is running (python server.py), you can connect it to various MCP clients:
Claude Desktop (Anthropic): Add your server config to Claude’s claude_desktop_config.json:
{
"mcpServers": {
"dataconnector": {
"command": "python",
"args": ["/path/to/server.py"]
}
}
}
Cursor: Add the same configuration to Cursor’s MCP settings.
Custom Python Client: Use the mcp package to build a client that connects to your server programmatically. This is useful for automated testing of your MCP tools before exposing them to a conversational AI.
Beyond FastMCP
FastMCP is great for getting started, but you can also build MCP servers using the lower-level mcp package directly. This gives you more control over:
- Transport protocol: stdio (for local tools) or HTTP/SSE (for remote servers)
- Custom JSON-RPC handling: if you need special error formats or middleware
- Resource subscriptions: letting the client subscribe to real-time updates from a resource
For example, if you want to build an MCP server that streams live data from a sensor or a stock price API, you would use HTTP/SSE transport so the client can receive push updates.
The Bottom Line
MCP is becoming the standard way to connect AI agents to external systems. By building an MCP server in Python, you give any MCP-compatible AI agent access to your tools, data, and APIs — without writing custom integration code for each AI model.
The key insight is that the AI does not need to understand your entire codebase. It just needs a clear description of each tool’s purpose and arguments, delivered through the MCP protocol. Good tool descriptions (docstrings) are just as important as the tool implementations.
Start simple: expose one or two functions as MCP tools, test with Claude Desktop or Cursor, and expand from there.
Sources: Dark Reading — Securing the AI-Powered DevOps Stack, Hospitality Net — Simple Booking MCP Connectors
Related reading: How to Build a Python MCP Server with FastMCP, Pydantic AI MCP Integration Options
Discussion
Leave a comment
No comments yet
Be the first to start the conversation.