Language models such as GPT-4, Llama, and Mistral have quickly become part of everyday technology. They can understand questions, generate natural-sounding text, and assist with tasks ranging from drafting emails to summarizing reports. Because of their versatility, many businesses are now integrating them into internal tools, customer-facing platforms, and automation systems.
As adoption continues to grow, the way models connect with live data and external systems is also changing. This is exactly where the Model Context Protocol (MCP) becomes important. Developed by Anthropic as an open-source standard, MCP offers a simple and consistent method for AI models to work with files, APIs, databases, and various tools — without needing separate, custom integrations.
In essence, MCP helps bridge the gap between intelligent language models and the dynamic environments they work in. It enables AI to stay current, take action, and adapt smoothly to real-world contexts — moving beyond text generation to become a true part of modern applications.
Artificial Intelligence is growing at an incredible pace. New language models are being released almost every few weeks and are finding real-world applications in areas like document summarization, content generation, and customer support automation. These models are impressive in what they can do, but they still work only with the information provided to them during a conversation. They don’t have direct access to live data, private databases, or real-time updates, which often limits their usefulness in dynamic, data-driven environments.
To overcome these limitations, developers have used methods like function calling—where AI models can trigger specific tools through predefined functions. However, this setup is often rigid and difficult to maintain. Every time a new tool is added or an existing one is updated, developers need to modify the code and redeploy it. Managing context manually also makes scaling such systems more complex.
The Model Context Protocol (MCP) offers a smarter solution. It introduces a standardized client-server design that separates tool management from the AI agent itself. In this setup, tools are hosted externally and made accessible through MCP servers. This allows AI systems to discover tools dynamically, access updated information, and maintain session-level memory. With MCP, AI agents can interact with fresh data, carry context across conversations, and handle complex workflows more efficiently—making them far more capable in real-world applications.
At the core of the architecture is the MCP Host, which represents the AI assistant that manages the language model itself. This could be a tool like Claude Desktop, an AI-integrated IDE, or any environment where an LLM needs to access external capabilities. The host is responsible for orchestrating which tools the model can use and when.
The Model Context Protocol (MCP) follows a modular architecture built on a host–client–server model, designed to enable secure and standardized communication between AI models and external tools or data sources. Each component in this system plays a distinct role in facilitating context-aware interactions.
1. MCP Host
The Host Process serves as the main coordinator that keeps the entire MCP system running smoothly. It serves as the runtime environment where the language model operates and is responsible for setting up and managing the overall toolchain. Specifically, the host:
2. MCP Clients
To communicate with external systems, the host relies on one or more MCP Clients. These clients serve as the bridge that forms a direct one-to-one link with individual MCP Servers. Because each client connects to only one server, communication stays clean, separate, and easy to manage. This design also improves precision, security, and reliability.
Each MCP Client is initialized and managed by the host and is responsible for maintaining a dedicated connection to a specific server. These clients are stateful components that:
3. MCP Server
The MCP Server is a lightweight service that exposes a specific functionality — such as querying a database, reading files from disk, or calling an external API — using the MCP protocol. Each server is purpose-built and scoped to a single capability or domain. For instance, you might have a dedicated “Filesystem Server” to expose local files or a “GitHub Server” to fetch repository metadata.
Each server is focused on a particular task and operates independently.
4. MCP Protocol
The MCP Protocol — often called the base protocol — lays out the fundamental rules for communication between MCP clients and servers. It explains how a connection should begin, how messages should be organized, and how data should be passed securely between both sides. With this in place, any MCP client can work smoothly with any MCP server, regardless of how each one is built, allowing tools and systems to integrate without friction.
5. Data sources
In MCP, the transport layer is responsible for managing the flow of messages between the client and the server. Think of it as the delivery system — it’s what ensures the two sides can talk to each other effectively.
MCP relies on JSON-RPC 2.0 for structuring its messages, while the transport layer ensures they are exchanged smoothly and dependably between the client and server.
Unlike the traditional HTTP request-response model — where each request is independent — MCP is built for stateful and long-running connections. That means the communication channel stays open, allowing continuous interaction between client and server without starting over each time.
MCP supports different transport methods depending on whether the communication is happening locally or over a network. Two main types are currently supported:
1. STDIO (Standard Input/Output) Transport
In the STDIO transport, the MCP client launches the MCP server as a child process running on the same machine. They communicate through standard input (STDIN) and output (STDOUT), just like command-line tools. The client sends messages to the server’s STDIN, and the server replies via STDOUT. All messages follow the JSON-RPC 2.0 format and are separated by newline characters.
You should use STDIO transport when you’re working on command-line tools or building features that run locally on the same machine. It’s a great choice for simple setups where the client and server just need to talk directly, like in shell scripts or lightweight tools. STDIO keeps things fast, straightforward, and doesn’t require any network setup — making it perfect for local integrations.
2. Streamable HTTP transport in MCP
Streamable HTTP is a transport method in MCP that lets the client and server talk to each other using standard HTTP — just like how websites and web APIs work. The client sends messages to the server using HTTP POST requests, while the server can reply through a live connection known as Server-Sent Events (SSE) to push messages back to the client.
This makes it ideal when you’re building web-based applications, tools that run in the browser, or any system that needs to communicate over the internet.
How Streamable HTTP Works
Here’s how both sides of the communication happen:
a. Client → Server (Sending Requests)
When the client needs to communicate with the MCP server, it sends the message through a standard HTTP POST request. Each request carries a JSON-RPC message, which tells the server what action to take or what data the client needs.
b. Server → Client (Receiving Responses)
In MCP, every reply sent by the server uses the JSON‑RPC message format. How these responses reach the client depends on the transport being used:
c. Server-Initiated Messages (Notifications and Requests)
MCP supports two-way communication using JSON-RPC, which means the server is not limited to responding only after a client request. Once a connection is established, the server can independently send JSON-RPC messages back to the client whenever required. These messages may include notifications, progress updates, or requests for additional input.
This bi-directional communication works across all supported transports. It can operate over a persistent stdio connection in local setups, through WebSockets, or via HTTP-based streaming mechanisms such as Server-Sent Events (SSE). As long as the underlying connection remains active, the server is free to push messages to the client in real time.
3. Custom Transports in the MCP
While MCP provides built-in transport options like STDIO and Streamable HTTP, it also gives you the flexibility to create your own custom transport — designed specifically for your use case.
A custom transport is any communication method you implement yourself, as long as it follows MCP’s standard Transport interface. This means that even if you’re not using STDIO or HTTP, your system can still speak MCP — as long as it sends and receives messages in the expected format.
Several popular AI tools offer support for MCP, including:
pip install langchain-groq
pip install langchain-mcp-adapters
pip install mcp # FastMCP server implementation
pip install langgraph
pip install python-dotenv
pip install requests
weather_mcp_server.py
A FastMCP server that provides a get_weather tool to fetch real-time city weather, returning temperature, humidity, and conditions in a structured format.
"""
weather_mcp_server.py
─────────────────────
FastMCP tool server that provides real-time weather information using OpenWeatherMap API.
Tool
----
• get_weather(city: str) -> dict
Given a city name, returns the current weather in metric units.
Run
---
$ pip install fastmcp httpx python-dotenv
$ export OPENWEATHERMAP_API_KEY=your_api_key
$ python weather_mcp_server.py
"""
import os, requests
import httpx
from dotenv import load_dotenv
from mcp.server.fastmcp import FastMCP
# ----------------------------------------------------------------------------------------------------------
# Load environment variables
# ----------------------------------------------------------------------------------------------------------
load_dotenv()
API_KEY = os.getenv("OPENWEATHERMAP_API_KEY")
URL = "https://api.openweathermap.org/data/2.5/weather"
# ----------------------------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------
# Create MCP server
# ----------------------------------------------------------------------------------------------------------
mcp = FastMCP("Weather")
# ----------------------------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------
# Register tool with FastMCP
# ----------------------------------------------------------------------------------------------------------
@mcp.tool() # <-- parentheses optional
def get_weather(city: str) -> dict:
"""Return the current weather for *city* (metric units)."""
params = {"q": city, "appid": API_KEY, "units": "metric"}
resp = requests.get(URL, params=params, timeout=10)
if resp.status_code != 200:
return {
"found": False,
"answer": f"Could not fetch weather for {city} "
f"(API status {resp.status_code})"
}
data = resp.json()
weather = data["weather"][0]["description"].capitalize()
temp = data["main"]["temp"]
feels = data["main"]["feels_like"]
humidity = data["main"]["humidity"]
return {
"found": True,
"answer": (
f"{weather}, {temp} °C (feels like {feels} °C) "
f"with {humidity}% humidity"
),
}
# ----------------------------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------
# Run MCP server using HTTP transport
# ----------------------------------------------------------------------------------------------------------
if __name__ == "__main__":
# “http” is the modern name for streamable HTTP
mcp.run(transport="streamable-http")
# ----------------------------------------------------------------------------------------------------------
sqlite_mcp_server.py
An MCP server that manages a simple SQLite people database, exposing tools to insert new records and fetch existing ones through natural language queries.
"""
sqlite_mcp_server.py
────────────────────────────
FastMCP server exposing two tools backed by a local SQLite DB of students.
Tools
-----
• insert_student_record(sql_insert_query: str) -> bool
• fetch_student_records(sql_select_query: str = "SELECT * FROM students") -> list[dict]
Run
---
$ python sqlite_student_mcp_server.py
"""
import pathlib
import sqlite3
from dotenv import load_dotenv
from mcp.server.fastmcp import FastMCP
# ----------------------------------------------------------------------------------------------------------
# Environment / constants
# ----------------------------------------------------------------------------------------------------------
load_dotenv()
DB_PATH = pathlib.Path("Students.db") # auto‑created if absent
# ----------------------------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------
# FastMCP server & tools
# ----------------------------------------------------------------------------------------------------------
mcp = FastMCP("SQLite‑Students‑DB")
# ----------------------------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------
# Connection helper
# ----------------------------------------------------------------------------------------------------------
def get_sqlite_connection(db_path):
"""
Open (or create) a SQLite database file and return a (connection, cursor).
Parameters
----------
db_path : pathlib.Path
Path to the SQLite file. If it does not exist, SQLite will create it.
Returns
-------
tuple
(sqlite3.Connection, sqlite3.Cursor)
Raises
------
sqlite3.Error
Propagates any low‑level SQLite error after logging a message.
"""
try:
# Establish a connection to the DB file (creates the file if absent)
connection = sqlite3.connect(db_path)
# Configure the connection so each result row behaves like a mapping
# (row["col_name"]) instead of a plain tuple.
connection.row_factory = sqlite3.Row
# Obtain a cursor object for executing SQL statements.
cursor = connection.cursor()
return connection, cursor
except sqlite3.Error as exc:
# Print diagnostic information and re‑raise so callers can handle it.
print(f"[DB] Connection error: {exc}")
return None, None
# ----------------------------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------
# Ensure table exists
# ----------------------------------------------------------------------------------------------------------
def ensure_student_table():
"""
Ensure the `students` table exists, then return an open (connection, cursor).
The table schema is:
id INTEGER PRIMARY KEY AUTOINCREMENT
name TEXT NOT NULL
gender TEXT NOT NULL
class TEXT NOT NULL
Returns
-------
tuple
(sqlite3.Connection, sqlite3.Cursor) with an open transaction.
Raises
------
sqlite3.Error
Propagates any SQL/DDL error after logging.
"""
# Step 1: open (or create) the database file
connection, cursor = get_sqlite_connection(DB_PATH)
try:
if connection is None or cursor is None:
return None, None
# Step 2: execute DDL to create the table if it doesn't already exist
cursor.execute(
"""
CREATE TABLE IF NOT EXISTS students (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
gender TEXT NOT NULL,
class TEXT NOT NULL
)
"""
)
# Step 3: commit DDL changes so the schema is persisted
connection.commit()
# Step 4: return the connection/cursor so callers can keep using them
return connection, cursor
except sqlite3.Error as exc:
# Roll back any partial transaction to keep DB consistent
if connection is not None:
connection.rollback()
print(f"[DB] Table‑creation error: {exc}")
return None, None
# ----------------------------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------
# Insert a new row into the students table.
# ----------------------------------------------------------------------------------------------------------
@mcp.tool()
def insert_student_record(sql_insert_query):
"""
Insert a new row into the students table.
Args
----
sql_insert_query : str
SQL INSERT statement, e.g.:
INSERT INTO students (name, gender, class)
VALUES ('John Doe', 'Male', '10A')
Returns
-------
bool
True if the insertion succeeds, False otherwise.
"""
# Get a database connection and cursor, ensuring the 'students' table exists
connection, cursor = ensure_student_table()
try:
# Check if connection or cursor creation failed
if connection is None or cursor is None:
# Log the failure to create connection or cursor
print("[DB] No connection or cursor")
return False
# Log the INSERT query to be executed
print(f"[DB] INSERT: {sql_insert_query}")
# Execute the user-provided SQL INSERT query
cursor.execute(sql_insert_query)
# Commit the transaction to persist the changes
connection.commit()
# Return True to indicate successful insertion
return True
# Handle any SQLite errors that occur during the insertion
except sqlite3.Error as exc:
# Log any SQLite error that occurs during insertion
print(f"[DB] Insert error: {exc}")
return False
finally:
# Close the database connection to release resources
if connection is not None:
connection.close()
# ----------------------------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------
# Fetch rows from the students table via a SELECT query.
# ----------------------------------------------------------------------------------------------------------
@mcp.tool()
def fetch_student_records(sql_select_query):
"""
Fetch rows from the students table via a SELECT query.
Args
----
sql_select_query : str, default "SELECT * FROM students"
A valid SQL SELECT statement to retrieve student records.
Returns
-------
list of dict
A list where each item is a dictionary representing one student row.
Example: [{'id': 1, 'name': 'Alice', 'gender': 'Female', 'class': '10A'}]
"""
# Get a connection and cursor to the students database
connection, cursor = ensure_student_table()
try:
# Check if connection or cursor failed to initialize
if connection is None or cursor is None:
print("[DB] No connection or cursor")
return []
# Log the SELECT query to be executed
print(f"[DB] SELECT: {sql_select_query}")
# Execute the user-provided SQL SELECT query
cursor.execute(sql_select_query)
# Extract column names from the cursor metadata
columns = [d[0] for d in cursor.description]
# Fetch all result rows from the query
rows = cursor.fetchall()
# Convert each row to a dictionary using column names
student_records = [dict(zip(columns, row)) for row in rows]
# Return the list of student records
return student_records
# Handle any SQLite errors that occur during the query
except sqlite3.Error as exc:
# Log any SQLite error that occurs during query execution
print(f"[DB] Select error: {exc}")
return []
finally:
# Close the database connection to release resources
if connection is not None:
connection.close()
# ----------------------------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------
# Run server over stdio
# ----------------------------------------------------------------------------------------------------------
if __name__ == "__main__":
mcp.run(transport="stdio")
# ----------------------------------------------------------------------------------------------------------
client.py
A client application that connects to multiple MCP servers, integrates them with a Groq-powered LLM agent, and lets users query both a people database and live weather data through natural language.
"""client.py
Run an MCP‑aware ReAct agent that can:
1. Query a local **people** database (stdio transport).
2. Fetch live weather from an HTTP Weather MCP server.
The script:
• Builds a MultiServerMCPClient from CONNECTIONS.
• Discovers all available tools (`await client.get_tools()`).
• Creates a Groq llama3 LLM.
• Wraps the LLM and tools in a ReAct agent.
• Asks two example questions: one database query, one weather query.
"""
# ----------------------------------------------------------------------------------------------------------
import asyncio # Async event‑loop utilities
from dotenv import load_dotenv
from langchain_groq import ChatGroq # Groq‑hosted LLM wrapper
from langgraph.prebuilt import create_react_agent # Helper to build ReAct agent
from langchain_mcp_adapters.client import MultiServerMCPClient # MCP multi‑server client
# ---------------------------------------------------------------------
# Environment
# ---------------------------------------------------------------------
load_dotenv() # reads .env and sets GROQ_API_KEY, etc.
# ----------------------------------------------------------------------------------------------------------
# MCP connection map
# ----------------------------------------------------------------------------------------------------------
CONNECTIONS = {
"Students_db": { # SQLite MCP server (stdio)
"command": "python",
"args": ["sqlite_mcp_server.py"],
"transport": "stdio",
},
"weather": { # Weather MCP server (HTTP)
"url": "http://localhost:8000/mcp",
"transport": "streamable_http",
},
}
# ----------------------------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------
# Main orchestration coroutine
# ----------------------------------------------------------------------------------------------------------
async def main() -> None:
"""Build the agent, run two example queries, and print the answers."""
# ----------------------------------------------------------------------------------------------------------
# Create an MCP client that manages communication with all tool servers
client = MultiServerMCPClient(CONNECTIONS)
# Discover every tool exposed by the configured servers
tools = await client.get_tools()
# Build the Groq LLM and the ReAct agent
llm = ChatGroq(model="llama3-8b-8192", temperature=0.0)
# Build a ReAct agent that can plan and invoke the discovered tools
agent = create_react_agent(llm, tools)
# ----------------------------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------
# Example 1: database query
# ----------------------------------------------------------------------------------------------------------
# user_query = "Show all students records" #
# print(f"\nUSER Query ➜ {user_query}") # Log the user question
# # Invoke the agent with the user message (returns a chain‑of‑thought dict)
# db_response = await agent.ainvoke(
# {"messages": [{"role": "user", "content": user_query}]}
# )
# # Print the assistant’s final answer
# print("Agent Response ➜ ", db_response["messages"][-1].content)
# ----------------------------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------
# Example 2: weather query
# ----------------------------------------------------------------------------------------------------------
user_weather = "what is the weather in Delhi?"
print(f"\nUSER Query ➜ {user_weather}") # Log the user question
# Ask the same agent about weather; it chooses the weather tool
weather_response = await agent.ainvoke(
{"messages": [{"role": "user", "content": user_weather}]}
)
# Print the weather summary produced by the agent
print("Agent Response ➜ ", weather_response["messages"][-1].content)
# ----------------------------------------------------------------------------------------------------------
# ----------------------------------------------------------------------------------------------------------
# Entrypoint
# ----------------------------------------------------------------------------------------------------------
if __name__ == "__main__":
# Run the async main coroutine in a new event loop
asyncio.run(main())
# ----------------------------------------------------------------------------------------------------------
1. Easy Integration
MCP provides a common way for AI models to connect with external tools, files, and data sources. You don’t need to create separate integrations for each model or application.
2. Reusable and scalable setup
Once an MCP server is in place, it can be reused for multiple models. You can easily add or update tools without changing your entire system.
3. Better context handling
MCP servers allow LLMs to access consistent, structured resources and state across multiple interactions. This helps the model maintain continuity and perform multi-step tasks more reliably.
4. Keeps systems organized and secure
Instead of letting AI models directly access many different APIs or databases, everything goes through the MCP server. This helps keep the architecture cleaner and easier to control.
5. Flexibility for different environments
MCP works well with different types of models and setups. It reduces dependency on a specific vendor or tool, making it easier to switch or upgrade your AI system in the future.
1. Security risks if not managed properly
Since MCP servers can access external data and perform actions, poor configuration or weak access control can lead to security issues or data leaks.
2. Extra layer of complexity
Running an MCP server adds another component to manage. It may require extra setup, monitoring, and can slightly increase response time.
3. Compatibility differences
Not all MCP implementations support the same features. You might face small inconsistencies when connecting different models or tools.
4. Context management can be tricky
Although MCP supports session-level memory, maintaining accurate context across multiple sessions or systems can be difficult in large deployments.
5. Risk of unintended actions
If the AI model triggers the wrong tool or a tool behaves unexpectedly, it could cause errors or unwanted changes, such as updating the wrong data
1. MCP Bridges the Gap Between LLMs and Real-World Tasks
This blog showed how the Model Context Protocol (MCP) turns a passive LLM into an interactive, action-capable agent. By connecting a language model to external tools — like a database or a live weather API — through MCP, we give the model the power to not just respond, but also retrieve, compute, and act.
2. Functionality Becomes Modular and Scalable
Instead of hard-coding tool logic directly into the application, we separated that logic into an MCP server. This clean separation allows developers to manage, update, or scale tools independently of the client. For example, if the SQLite schema changes or a new weather API is added, the MCP server can be updated without touching the client code at all.
3. Real-World Examples Show Practical Value
With just two tools — one that adds and fetches records from a SQLite database, and another that fetches current weather data for a given city — we have demonstrated how easy it is to give an LLM real-world capabilities. These tools show how MCP enables natural language prompts to trigger meaningful backend operations, from logging student data to accessing real-time weather.
4. MCP Encourages Clean, Maintainable Design
MCP naturally separates your model from the logic that powers it. Instead of mixing tool calls or business rules inside your application code, those capabilities live inside the MCP server. This separation makes the whole system easier to manage and extend.
Because tools can be discovered at runtime and the server can hold its own internal state when required, you can update or add functionality without touching the model or rewriting prompts. The LLM stays focused on reasoning, while the server handles the operational work, leading to a cleaner, more maintainable design overall.
5. A Foundation for Building Smarter AI Systems
As AI continues to evolve, MCP provides a powerful and flexible foundation for building smarter, context-aware, and task-driven systems. Whether you’re building command-line agents, web assistants, or enterprise applications, MCP gives your LLMs the ability to interact with the world — securely, scalably, and intelligently.
Reference Links
Date: Nov 28, 2025
Author: Shubham Salunke
Follow us