Build an MCP Server in Python and Connect It to Claude Desktop and SQLite
Build an MCP server in Python that wraps a SQLite database, register it with Claude Desktop on Windows, and let the model write and run its own SQL queries.
This article is published in English.
Sources: Stop Writing Custom APIs for Your AI Agents: Build an MCP Server in 5 Minutes
Anyone building enterprise agents knows the pain of writing glue code. One wrapper for the database, another integration layer for Jira tickets, another for Slack. Multiply that by ten databases and five LLM frameworks such as Claude, AutoGen and LangChain, and the design collapses under its own weight. This is the N-by-M integration problem: the AI must understand the exact shape of every data source it touches.
The Model Context Protocol (MCP) is an open standard aimed squarely at that problem. Picture a universal USB-C cable for AI. A database connection is written once and exposed through a single standardized server, and any compatible client can connect, discover the tools on offer and use them. What follows is a local MCP server in fewer than 50 lines of Python, connected to Claude Desktop and backed by a live SQLite database.
Prerequisites
The official Python mcp package does the heavy lifting. It hides the JSON-RPC details, so a single decorator is enough to expose a tool to the model. Install it in your virtual environment first:
pip install mcp
Claude Desktop also needs to be installed on the machine.
Writing the server
Make a file called mcp_server.py, import the libraries and initialize the server. One detail matters here: the os module pins the SQLite file to the same directory as the script. Claude Desktop launches the server in the background, and without a fixed location you can hit permission errors.
import sqlite3
import json
import os
import sys
from mcp.server.mcpserver import MCPServer
# Initialize the MCP server
mcp = MCPServer(name="Enterprise_SQL_Agent")
# Force the database to be created in the exact same folder as this script
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DB_PATH = os.path.join(BASE_DIR, "enterprise.db")
Next comes a throwaway database so the model has something to query. Setup logging goes to standard error (sys.stderr). Standard output is the channel MCP uses to talk to the client, and stray prints there would corrupt it.
def setup_dummy_db():
"""Create a sample employee database for the demo"""
try:
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute('''CREATE TABLE IF NOT EXISTS employees
(id INTEGER PRIMARY KEY, name TEXT, role TEXT, salary INTEGER)''')
cursor.execute("DELETE FROM employees")
employees = [
("Alice", "Data Scientist", 120000),
("Bob", "DevOps Engineer", 115000),
("Charlie", "AI Researcher", 135000)
]
cursor.executemany("INSERT INTO employees (name, role, salary) VALUES (?, ?, ?)", employees)
conn.commit()
conn.close()
print("Database initialized successfully.", file=sys.stderr)
except Exception as e:
print(f"Database setup error: {e}", file=sys.stderr)
Exposing the database as a tool
The core step is an ordinary Python function that runs a SQL query and returns the rows. Add the @mcp.tool() decorator and the server reads the docstring and type hints to build a JSON schema for the model automatically.
@mcp.tool()
def query_employee_database(sql_query: str) -> str:
"""
Executes a SQL SELECT query against the enterprise.db database.
The database contains an 'employees' table with columns:
- id (INTEGER PRIMARY KEY)
- name (TEXT)
- role (TEXT)
- salary (INTEGER)
SECURITY: Only READ operations (SELECT) are permitted.
"""
# Safety Check: Block destructive SQL commands
dangerous_keywords = ["DROP", "DELETE", "UPDATE", "INSERT", "ALTER"]
if any(keyword in sql_query.upper() for keyword in dangerous_keywords):
return "Error: Only SELECT queries are authorized for this tool."
try:
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute(sql_query)
results = cursor.fetchall()
# Format the output as JSON so the LLM can read it cleanly
column_names = [description[0] for description in cursor.description]
formatted_results = [dict(zip(column_names, row)) for row in results]
conn.close()
return json.dumps(formatted_results, indent=2)
except Exception as e:
return f"Database error: {str(e)}"
A short startup block finishes the file:
if __name__ == "__main__":
setup_dummy_db()
mcp.run()
Connecting Claude Desktop
The client has to be told where the server lives. On Windows, go to the %APPDATA%\Claude folder, open claude_desktop_config.json in an editor and add an mcpServers entry.
A Windows detail that trips people up: if you run inside a virtual environment such as Conda or venv, give the absolute path to that environment's python.exe instead of the bare global python command, and write every path with doubled backslashes.
{
"mcpServers": {
"enterprise-sql": {
"command": "C:\\Users\\YourName\\.conda\\envs\\your_env\\python.exe",
"args": [
"D:\\Your\\Project\\Path\\mcp_server.py"
]
}
}
}
Save the file and restart Claude Desktop completely.
Seeing it work
Open Claude Desktop and look for the hammer icon (Tools) at the bottom right of the chat box. The tool query_employee_database should show up there already registered. A good first test is asking who earns the most in the employees table and what job that person holds.
The model reads the tool description, writes the SQL itself (SELECT * FROM employees ORDER BY salary DESC LIMIT 1;), runs it through your server and turns the result into a plain-language answer. No custom API layer was written, and the schema was discovered dynamically.
The same pattern extends to Slack, Jira, GitHub or any internal company API, and that's what changes the economics of building agentic systems in a company: each system is wrapped once, and every compatible assistant can use it.