

By Matej Škrabić on July 8, 2025
Model Context Protocol (MCP) is an open standard introduced by Anthropic with the goal of standardizing how AI applications (chatbots, IDE assistants, or custom agents) connect with external tools, data sources and systems.
In this blog, we will explain the details of the protocol and provide a step-by-step tutorial on how to build an AI agent that connects to a SQL database via MCP, comparing this approach to the same solution without using MCP.
Let’s dive straight in!
In the same way REST APIs standardized the way web applications interact and exchange data, MCP aims to standardize the way LLMs communicate with external tools, data sources and systems. The general architecture of the protocol consists of:
For more details on the protocol architecture, layers and components, we suggest the official MCP documentation that can be found here. This blog will provide a step-by-step tutorial to guide you through your first MCP-based AI agent implementation.
A common usage for MCP is orchestrating communication between an LLM and different data sources - databases, cloud storage, web applications, etc. For simplicity, we will use a SQLite database and shift our focus on implementing the MCP-based communication with a LLM.
To begin, we will create a SQLite database containing mock data about food sales. Let’s first create the necessary tables:
# Necessary imports
import os
import sqlite3
# Creating a database file and opening a connection
conn = sqlite3.connect(
os.path.abspath("./data/food_sales.db")
)
cursor = conn.cursor()
# Create tables
# Products
cursor.execute("""
CREATE TABLE IF NOT EXISTS products (
product_id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT,
unit_price REAL NOT NULL,
unit TEXT NOT NULL
);
""")
# Customers
cursor.execute("""
CREATE TABLE IF NOT EXISTS customers (
customer_id INTEGER PRIMARY KEY AUTOINCREMENT,
company_name TEXT NOT NULL,
address TEXT NOT NULL,
contact_person TEXT,
phone TEXT,
email TEXT
);
""")
# Orders
cursor.execute("""
CREATE TABLE IF NOT EXISTS orders (
order_id INTEGER PRIMARY KEY AUTOINCREMENT,
order_date TEXT NOT NULL,
customer_id INTEGER NOT NULL,
delivery_date TEXT,
FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);
""")
# Products in each order
cursor.execute("""
CREATE TABLE IF NOT EXISTS order_items (
order_item_id INTEGER PRIMARY KEY AUTOINCREMENT,
order_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
quantity INTEGER NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(order_id),
FOREIGN KEY (product_id) REFERENCES products(product_id)
);
""")
# Payments
cursor.execute(
"""
CREATE TABLE IF NOT EXISTS payments (
payment_id INTEGER PRIMARY KEY AUTOINCREMENT,
order_id INTEGER NOT NULL,
payment_date TEXT NOT NULL,
amount_paid REAL NOT NULL,
payment_method NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(order_id)
);
""")
So, we’ve created 5 tables:
Now, we can fill it with some mock data.
products = [
("Packaged Rice", "5kg bag of long grain rice", 12.0, "bag"),
("Bottled Water", "12-pack of 1L bottles", 6.5, "pack"),
("Cooking Oil", "2L bottle of vegetable oil", 4.2, "bottle"),
("Instant Noodles", "Box of 30 packets", 10.0, "box"),
("Canned Beans", "12-pack of 400g cans", 8.0, "pack"),
("Flour", "10kg bag of all-purpose flour", 9.5, "bag"),
("Sugar", "5kg bag of refined sugar", 7.0, "bag"),
("Salt", "1kg pack of iodized salt", 1.0, "pack"),
("Tomato Paste", "6-pack of 500g cans", 5.5, "pack"),
("Tea Bags", "100-count box of black tea", 3.8, "box")
]
cursor.executemany(
"INSERT INTO products (name, description, unit_price, unit) VALUES (?, ?, ?, ?);",
products
)
clients = [
(
"Acme Grocers",
"123 Market Street",
"John Doe",
"123-456-7890",
"john@acmegrocery.com"
),
(
"Beta Mart",
"456 Retail Road",
"Jane Smith",
"234-567-8901",
"jane@betamart.com"
),
(
"Gamma Wholesalers",
"789 Supply Blvd",
"Alice Johnson",
"345-678-9012",
"alice@gammawholesale.com"
),
(
"Delta Foods",
"321 Distribution Ave",
"Bob Brown",
"456-789-0123",
"bob@deltafoods.com"
),
(
"Echo Supermarket",
"654 Grocery Lane",
"Carol White",
"567-890-1234",
"carol@echosupermarket.com"
),
(
"Foxtrot Distributors",
"987 Warehouse Rd",
"David Black",
"678-901-2345",
"david@foxtrotdist.com"
),
(
"Global Retailers",
"213 Commerce St",
"Eve Green",
"789-012-3456",
"eve@globalretail.com"
),
(
"Helix Market",
"435 Consumer Pkwy",
"Frank Blue",
"890-123-4567",
"frank@helixmarket.com"
),
(
"Ion Foods",
"876 Fresh Dr",
"Grace Yellow",
"901-234-5678",
"grace@ionfoods.com"
),
(
"Jetstream Grocery",
"132 Wholesale Rd",
"Henry Cyan",
"012-345-6789",
"henry@jetstreamgrocery.com"
)
]
cursor.executemany(
"INSERT INTO clients (company_name, address, contact_person, phone, email) \
VALUES (?, ?, ?, ?, ?);",
clients
)
orders = [
{
"customer_id": 1,
"order_date": "2025-05-01",
"delivery_date": "2025-05-05",
"items": [(1, 10), (2, 5)] # (product_id, quantity)
},
{
"customer_id": 2,
"order_date": "2024-02-03",
"delivery_date": "2024-02-07",
"items": [(3, 8), (4, 12)]
},
{
"customer_id": 3,
"order_date": "2024-05-10",
"delivery_date": "2024-05-14",
"items": [(5, 6), (6, 10)]
},
{
"customer_id": 4,
"order_date": "2024-05-12",
"delivery_date": "2024-05-16",
"items": [(7, 15), (8, 20)]
},
{
"customer_id": 5,
"order_date": "2024-05-15",
"delivery_date": "2024-05-19",
"items": [(9, 7), (10, 10)]
},
{
"customer_id": 2,
"order_date": "2025-06-10",
"delivery_date": "2025-06-12",
"items": [(1, 8), (3, 6)]
},
{
"customer_id": 1,
"order_date": "2024-05-22",
"delivery_date": "2024-05-25",
"items": [(2, 10), (5, 4)]
},
{
"customer_id": 3,
"order_date": "2024-05-25",
"delivery_date": "2024-05-28",
"items": [(6, 7), (9, 9)]
},
{
"customer_id": 4,
"order_date": "2024-05-27",
"delivery_date": "2024-05-30",
"items": [(4, 11), (8, 14)]
},
{
"customer_id": 5,
"order_date": "2024-05-29",
"delivery_date": "2024-06-01", "items": [(7, 13), (10, 8)]
}
]
# Get product prices
product_prices = {
row[0]: row[1]
for row in cursor.execute(
"SELECT product_id, unit_price FROM products"
).fetchall()
}
# Insert orders and related items
for order in orders:
cursor.execute(
"INSERT INTO orders (order_date, customer_id, delivery_date) \
VALUES (?, ?, ?);",
(order["order_date"], order["customer_id"], order["delivery_date"])
)
order_id = cursor.lastrowid
order_total = 0
for product_id, quantity in order["items"]:
cursor.execute(
"INSERT INTO order_items (order_id, product_id, quantity) \
VALUES (?, ?, ?);",
(order_id, product_id, quantity)
)
order_total += product_prices[product_id] * quantity
# Fixed payment: 2 days after delivery
payment_date = datetime.fromisoformat(order["delivery_date"]) + timedelta(days=2)
payment_method = random.choice(['Credit Card', 'Bank Transfer', 'Cash', 'Cheque'])
cursor.execute(
"INSERT INTO payments (order_id, payment_date, amount_paid, payment_method) \
VALUES (?, ?, ?, ?);",
(order_id, payment_date.isoformat(), round(order_total, 2), payment_method)
)
conn.commit()
conn.close()
Voilà! Our database is ready. Let’s move on to the main part of this tutorial, which is building the AI agent.
To showcase the value of MCP, we will first build a solution using LangChain, with no MCP. Let’s say we want to find all customers who spent more than $50 since the beginning of May 2025, and return their most purchased item name and the quantity they purchased. A LangChain-based solution might look something like this:
from dotenv import load_dotenv
from langchain_community.agent_toolkits.sql.toolkit import SQLDatabaseToolkit
from langchain_community.agent_toolkits.sql.toolkit import SQLDatabase
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
DB_PATH = "./data/food_sales.db"
SYSTEM_MESSAGE = """
You are an agent designed to interact with a SQL database.
Given an input question, create a syntactically correct {dialect} query to run,
then look at the results of the query and return the answer. Unless the user
specifies a specific number of examples they wish to obtain, always limit your
query to at most {top_k} results.
You can order the results by a relevant column to return the most interesting
examples in the database. Never query for all the columns from a specific table,
only ask for the relevant columns given the question.
You MUST double check your query before executing it. If you get an error while
executing a query, rewrite the query and try again.
DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the
database.
To start you should ALWAYS look at the tables in the database to see what you
can query. Do NOT skip this step.
Then you should query the schema of the most relevant tables.
""".format(
dialect="SQLite",
top_k=5,
)
def main():
load_dotenv()
# Set up the database toolkit
db = SQLDatabase.from_uri(f"sqlite:///{DB_PATH}")
llm = ChatOpenAI(model="gpt-4o")
toolkit = SQLDatabaseToolkit(db=db, llm=llm)
tools = toolkit.get_tools()
# Create SQL agent
agent_executor = create_react_agent(llm, tools, prompt=system_message)
question = """
Find all customers that spent a total of more than $50 on orders since
the beginning of May 2025, and return the item they purchased the most
along with the purchased quantity.
"""
for step in agent_executor.stream(
{"messages": [{"role": "user", "content": question}]},
stream_mode="values"
):
step["messages"][-1].pretty_print()
if __name__ == "__main__":
main()
We print out each step in the Agent execution stream, but the final output should look something like this:
Here are the customers who spent more than $50 on orders since the beginning
of May 2025, along with the item they purchased the most and the quantity:
1. Acme Grocers: Purchased "Packaged Rice" with a quantity of 10.
2. Beta Mart: Purchased "Packaged Rice" with a quantity of 8.
For our MCP server, we will use the community SQLite server available here. For simplicity, we make a small modification to the server code:
ServerWrapper
in server.py
server.py
# class ServerWrapper():
# """A wrapper to compat with mcp[cli]"""
# def run(self):
# asyncio.run(main("test.db"))
# wrapper = ServerWrapper()
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"--db",
type=str,
help="Path to SQLite database file",
default="./data/food_sales.db"
)
args, _ = parser.parse_known_args()
asyncio.run(main(os.path.abspath(args.db)))
For our MCP client, we will use the open-source client library mcp-use. The initialization code now looks like this:
from loguru import logger
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from mcp_use import MCPAgent, MCPClient
# Load environment variables
# In an .env file, specify your OPENAI_API_KEY
load_dotenv()
# Create configuration dictionary
config = {
"mcpServers": {
"sqlite": {
"command": "python",
"args": [
"servers/mcp_server_sqlite/server.py",
"--db",
"./data/food_sales.db",
]
}
}
}
# Create MCPClient from configuration dictionary
client = MCPClient.from_dict(config)
logger.info("MCP client initialized")
# Create LLM
llm = ChatOpenAI(model="gpt-4o")
logger.info("LLM model initialized")
# Create agent with the client
agent = MCPAgent(llm=llm, client=client, max_steps=30)
And we’re ready! Let’s repeat our question - find all customers who spent more than $50 since May 2025, and return their favourite product. We will add some output formatting to the prompt as well.
result = await agent.run(
f"""
Find all customers that spent a total of more than $50 on orders since
the beginning of May 2025, and return the item they purchased the most
along with the purchased quantity.
Format the response as:
Customer ID: <customer_id>
Customer: <customer name>
Favourite product: <product name> (ID=<product_id>)
Quantity purchased: <product quantity since May 2025>
Price: <amount spent on that product since May 2025>
Total: <total amount spent by the customer since May 2025>
Return only this, with no additional text.
"""
)
print(result)
You should get the following result:
Customer ID: 1
Customer: Acme Grocers
Favourite product: Packaged Rice (ID=1)
Quantity purchased: 10
Price: $120.0
Total: $152.5
Customer ID: 2
Customer: Beta Mart
Favourite product: Packaged Rice (ID=1)
Quantity purchased: 8
Price: $96.0
Total: $121.2
At first, it may seem like we had to do approximately the same amount of coding in both approaches. But now imagine we have different types of tools (SQL, web-scraping, other LLMs, etc.) and many different data sources - with native LangChain we would have to build complex chains with different tool implementations and connections. With MCP - all functionalities share the same “interface”, and the agent knows how to communicate with them by default.
MCP is a relatively new protocol, and its long-term adoption and impact within the broader AI and agent development communities remain to be seen. As with many emerging technologies, its success will depend on factors such as usability, interoperability, community support, and the ecosystem that grows around it.
While it’s still early days, MCP represents a significant step toward standardizing how agents interact and operate, especially as AI systems become more modular, distributed, and capable of complex coordination. Regardless of how it evolves, one thing is clear: MCP has carved out a meaningful niche in the fast-moving landscape of artificial intelligence and autonomous agents, and its presence is likely to influence future developments in this space.
Technical and business lessons from real AI projects
Partner with us to develop an AI solution specifically tailored to your business.
Contact us