Build a 100% Local and Secure MCP Client in Just 10 Minutes
Welcome to this article. Today, we are going to build a 100% local, secure, and private MCP client that you can connect to any MCP server out there.
Here's what we're going to cover in this guide: - We'll start with some background on MCP, understanding some of the key components and how a client and server interact. - Then, I'll explain the architecture of the app we are going to build, which includes our local MCP client and an SQLite database over which we will build a server. - Finally, I'll provide a detailed walkthrough of the code so you can build this yourself, step-by-step.
Let's get started.
Understanding MCP and the Need for a Local Client
MCP (Model Context Protocol) is a standardized way to connect your LLMs or AI agents to external APIs, data sources, and various other tools. You can think of it as a USBC port for your AI application. You have your AI app, and you can easily plug and play a database, an MCP server built over GitHub or Gmail, a local file system, or any API on the internet using a standard interface.
There are two major components in an MCP setup: 1. MCP Server: This component wraps external APIs, databases, or any internet-accessible API. It can also be your local file system. It presents them as MCP tools and makes them available to the client. 2. MCP Client: This is used by our AI application, agents, and LLMs to communicate with the MCP server.
Typically, this MCP client is hosted by a Claud desktop or an IDE. The problem is that these MCP hosts often use an external LLM, meaning they call an API and send your data somewhere else. If you're working with sensitive data where privacy is paramount, you cannot use a client that sends your data to an external server. This is the exact problem we are going to solve. We'll be building our own local client that you can universally connect to any MCP server.
System Architecture Explained
Before we jump into the implementation, it's important to understand the key components and the architecture of the system we are building. We are creating a local MCP host that features a Llama Index agent, powered by a locally running LLM using Ollama. This agent works together with our custom-made client.
Here is the step-by-step lifecycle of a user interaction with the system: 1. User Query: A user initiates a query. 2. Agent Receives Query: The query is received by our local agent. 3. Client Connects to Server: The agent uses the client to connect with the local MCP server and retrieve information about all available tools. 4. Tool Selection: Based on the user's query, the agent decides which tool is the right one to call. 5. Function Calling: The agent performs the actual tool call or function call.
Our MCP server, built on top of an SQL database, exposes a couple of tools. However, you can replace this with any server, as our client is generic and universal, connecting easily without any code changes on the client side. For simplicity, we have two tools in our server: one to add data and one to read data.
- Response Generation: Based on the tool called, a context or response is generated.
- Final Answer: This response is received back by the agent, which then provides a final answer to the user's query.
The tech stack for this project includes Llama Index for orchestration and Ollama to locally serve the LLM (in this case, deepseek-coder-v2
) that powers our agent.
Building the System: A Step-by-Step Guide
Now, let's dive into the code and build the system.
The MCP Server
First, let's look at the server we'll use for this demo. It's a simple SQLite server with two tools: add_data
and read_data
. Each tool expects an SQL query as a string. We've provided detailed descriptions and examples to make it easier for our local LLM to make the tool calls.
Here is the core functionality for the add_data
tool:
```python
Add new data to our SQLite server
def adddata(sqlquery: str): # ... implementation to execute the insert query ... ```
And here is the read_data
tool:
```python
Read data from the SQLite server
def readdata(sqlquery: str): # ... implementation to execute the select query ... ```
The tools are straightforward. You can replace this server with any other you want to connect with our client without needing to change the client-side code.
To run the server, you use the following command. We'll use SSE (Server-Sent Events) for transport, which is a more sophisticated way to handle communication, especially for remote servers.
bash
ue run server.py --transport sse
With the server started, the next step is to build the MCP client and establish a connection.
The Local MCP Client
Llama Index provides two key modules for this: BasicMCPClient
and MCPToolSpec
.
We'll use BasicMCPClient
to instantiate our client and point it to our running server.
```python
from llamaindex.multimodal.base import BasicMCPClient
from llama_index.tools.mcp import MCPToolSpec
Create the MCP client
client = BasicMCPClient(
base_url="http://localhost:8000",
transport="sse" # Connect via Server-Sent Events
)
Now that our client is ready, we'll use `MCPToolSpec`. This class gets the tools from the MCP client and converts them into Llama Index function tools.
python
Get tools from the client
mcptools = MCPToolSpec(mcpclient=client) ``` These two lines handle all the heavy lifting of creating a client, connecting it to the server, and wrapping the tools so they can be used by our Llama Index agent.
Let's print the available tools to see the metadata. ```python
Print available tools and their metadata
availabletools = mcptools.gettoolsspec()
print(availabletools)
The output will show the tool names and the descriptions we provided, which the LLM will use to decide which tool to call.
Tool Name: adddata_tool
Description: Adds new data to the SQLite server. Expects an SQL query...
Tool Name: readdatatool Description: Reads data from the SQLite server. Expects an SQL query... ```
Building the Function-Calling Agent
With access to the server's tools, it's time to build the agent. First, we'll define a system prompt to guide the LLM on its purpose.
python
system_prompt = "You are an AI assistant that is used for tool calling. Your purpose is to help users interact with a database by adding and retrieving data."
Next, we define our function-calling agent. This is a standard Llama Index agent, but we provide it with the tools from our MCP client.
```python
from llama_index.agent import FunctionCallingAgent
The agent doesn't care if the tools are from a simple Python function
or from an MCP server.
agent = FunctionCallingAgent.fromdefaults(
name="DatabaseAgent",
description="An agent that can interact with a database.",
tools=mcptools.totoollist(), # <-- Here we pass the MCP tools
llm=localllm, # Your locally running LLM
systemprompt=systemprompt
)
``
The key is using
mcptools.totoollist()` to provide the tools from our server to the agent.
We also need a helper function to handle user messages and maintain chat context, allowing for follow-up questions. This function will manage the chat history, tool calls, and responses.
python
def handle_user_message(message, agent, context):
# ... implementation to manage chat history and interaction ...
# This stores tool info, raw output, and final responses.
Interacting with the Local Agent
Now it's time to interact with our agent, which is running completely locally and has access to the tools on our MCP server.
First, let's add some data with a natural language query. User Query:
"Add a record for Rafael Nadal, who is 39 years old and is a tennis player."
The agent successfully calls the add_data
tool with the correct SQL query arguments and confirms the action.
Agent Response:
"The data has been successfully added."
Now, let's try to fetch this data. User Query:
"Show me the data for Rafael Nadal."
The agent calls the correct read_data
tool and displays the result in a structured table.
Agent Response:
ID | Name | Age | Profession ---|---|---|--- 1 | Rafael Nadal | 39 | Tennis Player
Let's add more data. User Query:
"Add a record for a new person."
Agent Response:
"New data added."
And fetch all records again. User Query:
"Fetch all the data."
The agent retrieves and displays all the new records from the database.
Conclusion
This guide demonstrates a powerful setup for building a local MCP client that can be universally connected to any MCP server. You barely have to make any changes on the client side, as MCP standardizes all these interactions. This approach ensures your data remains private and secure while leveraging the power of AI agents and external tools.
Join the 10xdev Community
Subscribe and get 8+ free PDFs that contain detailed roadmaps with recommended learning periods for each programming language or field, along with links to free resources such as books, YouTube tutorials, and courses with certificates.