How to Build a Local MCP Server With FastMCP and a LangChain Agent
A beginner-friendly, step-by-step tutorial for building your own local MCP server with FastMCP and connecting it to a LangChain v1 agent running on Ollama, alongside a public remote MCP server.
The Model Context Protocol (MCP) is, in its own maintainers’ words, an open-source standard for connecting AI applications to external tools and data: the same idea as a USB port, but for AI agents instead of hardware. Anthropic open-sourced MCP, and adoption has moved fast enough that companies now expose MCP servers alongside their regular APIs, and independent services such as DeepWiki (from the team behind the AI software engineer Devin) run free, public MCP servers that anyone can connect to today, no account required. The practical question for a developer is no longer “should I learn MCP” but “how do I actually build one of these servers, and wire it into an agent I control.”
Table Of Content
- What you will learn: MCP, servers, and tools in plain terms
- Prerequisites
- Step 1: Install Ollama and pull a local chat model
- Step 2: Create a project and install the Python dependencies
- Step 3: Build the local MCP server with FastMCP
- Step 4: Write the agent that connects to a local and a remote MCP server
- Step 5: Run the agent and watch it choose tools
- Common mistakes and how to catch them
- Verifying it works end-to-end
- Next steps
- Sources
By the end of this walkthrough, you will have built your own local MCP server with two custom tools, connected a LangChain v1 agent to that server and to DeepWiki’s public MCP server at the same time, and run a real interactive session where a local model decides, on its own, which tool to call for which question. Every command below is cross-checked against the official Model Context Protocol documentation, FastMCP’s own documentation, the langchain-mcp-adapters GitHub repository, and DeepWiki’s MCP server documentation, and builds on a working example originally published by freeCodeCamp.
What you will learn: MCP, servers, and tools in plain terms
Before MCP, most simple AI agents hard-coded their tools directly inside the agent’s own script. That works for a single throwaway project, but it does not scale: every new agent has to reimplement the same “get the current time” or “search this database” logic from scratch, and there is no way for someone else’s agent to reuse a tool you already built.
MCP fixes this by standardizing the interface between an AI application and the tools it uses. An MCP server is a small program that exposes a list of tools (and optionally resources and prompts) over a defined protocol. An MCP client is anything that connects to that server (typically an AI agent) and lets a language model call those tools. Because the interface is standardized, any MCP-compatible client can use any MCP-compatible server, regardless of who wrote either side.
MCP servers are commonly exposed over two transports: stdio, where the server runs as a subprocess of the client and the two talk over standard input/output (ideal for local tools that only your own agent needs) and HTTP (specifically “Streamable HTTP” in the current spec), where the server runs as a network service that any client can connect to remotely. You are about to use both in the same agent: your own tools will run locally over stdio, and DeepWiki’s tools will run remotely over HTTP.
FastMCP is the Python framework this tutorial uses to build the server side. It turns writing an MCP server into something that feels like writing a small FastAPI app: you decorate an ordinary Python function with @mcp.tool, and FastMCP handles the protocol plumbing (the JSON-RPC messages, the tool schema generation, and the transport layer) for you. On the client side, langchain-mcp-adapters is the official LangChain library that connects to one or more MCP servers and loads their tools into a format LangChain v1’s create_agent function can use directly, so the agent code never has to care whether a given tool lives in a local subprocess or on someone else’s server halfway across the internet.
Prerequisites
- Operating system: commands below are written for Ubuntu 24.04 LTS or newer (Debian-based Linux in general). Ollama, FastMCP, and every Python package used here also ship macOS and Windows builds, so the same steps apply with only the install command in Step 1 changing.
- Hardware: a machine that can run a small local language model. This tutorial uses a 4-billion-parameter model; if your machine has limited RAM, a smaller model tag (noted in Step 1) still works, just with lower answer quality.
- Python: Python 3.10 or newer, with
python3 -m venvavailable (it ships with the standard library on any normal Python install). - Internet access: required once, to download Ollama and pull the model, and continuously for any question that needs DeepWiki’s hosted tools (your local tools and the model itself work fully offline afterward).
- Prior knowledge assumed: comfort running commands in a terminal and reading basic Python. No prior experience with MCP, FastMCP, or LangChain is assumed: each concept is explained the first time it appears.
Step 1: Install Ollama and pull a local chat model
Ollama is a tool for running open-weight language models directly on your own machine, so the model weights, your prompts, and the agent’s reasoning never leave your computer; the only thing that touches the network later on is whatever the model explicitly decides to send to a remote tool, and only when it decides to call one. Install it with the official Linux install script:
# Context: Ubuntu 24.04 LTS or newer, sudo access.
# Purpose: download and install Ollama as a local service.
curl -fsSL https://ollama.com/install.sh | sh
Expected output: the script downloads the Ollama binary, installs it, and sets it up to run as a background service. Confirm it is active with:
# Context: same machine, right after installation.
# Purpose: confirm the Ollama service is installed and print its version.
ollama --version
Expected output: a line such as ollama version is 0.x.x. The installer places the binary in a directory already on your PATH (it picks from /usr/local/bin, /usr/bin, or /bin), so it does not need to modify PATH itself, but some shells cache command locations from before you ran the installer. If ollama --version still fails with “command not found,” run hash -r (bash/zsh) to clear that cache, or simply open a new terminal.
Now pull a chat model. This tutorial uses Qwen 3.5, because it has native tool-calling support, the specific capability that lets a model decide when to call one of your MCP tools instead of just answering from memory:
# Context: Ollama installed and running.
# Purpose: download the 4B-parameter tag of Qwen 3.5 for local inference.
ollama pull qwen3.5:4b
Expected output: a series of progress bars as each model layer downloads, ending in a success line. If your machine is short on RAM, substitute the much smaller qwen3.5:0.8b tag: Qwen 3.5 ships size tags from 0.8B up to 122B parameters, so you can trade answer quality for a lighter footprint.
Verify the model is actually installed and ready to use:
# Context: after ollama pull finishes.
# Purpose: list every model currently available to Ollama on this machine.
ollama list
Expected output: a table listing qwen3.5:4b (or whichever tag you pulled) along with its size and the date it was last modified. If the table is empty, the pull above did not complete successfully: rerun it and watch for an error message.
Step 2: Create a project and install the Python dependencies
Isolate this project’s Python packages in a virtual environment so they cannot collide with anything else installed on your system; this matters more than usual here, because one of the packages below has a specific minimum version requirement:
# Context: a directory where you want the project to live.
# Purpose: create the project folder and an isolated Python environment inside it.
mkdir mcp-agent-tutorial && cd mcp-agent-tutorial
python3 -m venv venv
source venv/bin/activate
Expected output: your shell prompt gains a (venv) prefix, confirming the virtual environment is active. Every pip install and every python command from here on should be run from inside this activated shell.
# Context: venv activated, in the mcp-agent-tutorial directory.
# Purpose: install FastMCP, LangChain v1, its Ollama integration, and the MCP adapter.
pip install fastmcp langchain langchain-core langchain-ollama langchain-mcp-adapters
Expected output: pip resolves and installs each package plus its dependencies, ending with a line starting Successfully installed that lists fastmcp, langchain, langchain-core, langchain-ollama, langchain-mcp-adapters, and their transitive dependencies. This tutorial requires langchain>=1.0.0: LangChain v1 introduced the simplified create_agent function this tutorial relies on, and it does not exist in the same form in older releases.
Here is what each package is actually for, since you will use every one of them in the next two steps: fastmcp is the framework you will use to build your own MCP server; langchain and langchain-core provide the agent framework, including create_agent; langchain-ollama is LangChain’s official integration for talking to a locally running Ollama model; and langchain-mcp-adapters is the bridge that turns tools from one or more MCP servers (local or remote) into tools a LangChain agent can call directly, without the agent code needing to know or care where each tool actually runs.
Confirm FastMCP installed correctly before moving on:
# Context: same activated venv.
# Purpose: print FastMCP's version and confirm the install succeeded.
fastmcp version
Expected output: a short report listing the FastMCP version, the underlying MCP protocol version, and the Python version FastMCP is running on.
Step 3: Build the local MCP server with FastMCP
Your local server will expose two small, genuinely useful tools: current_time, which returns the current date and time, and word_count, which counts the words in a piece of text. Neither needs any external service, which makes them a clean way to see the full MCP round-trip without extra moving parts.
Save the following as mcp_server.py in your project directory:
# Context: mcp-agent-tutorial/, venv activated, fastmcp installed.
# Purpose: define a local MCP server exposing two tools over stdio.
from datetime import datetime
from fastmcp import FastMCP
mcp = FastMCP("local-tools")
@mcp.tool
def current_time() -> str:
"""Return the current local date and time.
Use this when the user asks what time or date it is.
"""
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
@mcp.tool
def word_count(text: str) -> int:
"""Count the number of words in a piece of text.
Use this when the user asks how long a piece of writing is or
asks you to count the words in something they've shared.
Returns the word count as an integer.
"""
return len(text.split())
if __name__ == "__main__":
# Run the MCP server over stdio.
mcp.run()
The detail that matters most in this file is the docstrings. FastMCP generates each tool’s schema (its name, its parameter types, and its description) automatically from the function’s type hints and its docstring. That generated description is exactly what the language model sees when it is deciding whether a given tool is the right one to call for a given question. A vague docstring like """counts words""" gives the model much less to work with than the fuller version above, which spells out when to use the tool and what it returns.
Before wiring this into an agent, do a quick sanity check that the file has no syntax errors:
# Context: same directory as mcp_server.py.
# Purpose: confirm the server starts cleanly before an agent ever launches it.
python3 mcp_server.py
Expected output: a single startup log line confirming FastMCP is running the local-tools server over the stdio transport, and then the terminal appears to hang. That is correct, not a bug: a stdio server sits and waits for a client to write JSON-RPC messages to its standard input. Press Ctrl+C to stop it. You do not need to leave it running: in the next step, your agent will launch this exact file itself, automatically, as a subprocess.
Step 4: Write the agent that connects to a local and a remote MCP server
The agent you are about to write coordinates three separate processes: your mcp_server.py from Step 3, launched as a local subprocess over stdio; DeepWiki’s public MCP server, reached over HTTPS; and the LangChain agent itself, which merges tools from both into one list and lets the model pick whichever it needs. DeepWiki’s MCP server is free, requires no API key or signup, and exposes three tools for asking questions about any public GitHub repository: read_wiki_structure, read_wiki_contents, and ask_question. Its documented endpoint for the modern “Streamable HTTP” transport is https://mcp.deepwiki.com/mcp.
It is worth being precise about what “local” means once a remote server is in the mix: your model’s own reasoning and every local-tool call stay entirely on your machine, but the moment the agent decides to call a DeepWiki tool, that specific request (and only that request) goes out over the network to DeepWiki’s servers.
Save the following as agent_with_mcp.py, in the same directory as mcp_server.py:
# Context: mcp-agent-tutorial/, venv activated, mcp_server.py already saved alongside this file.
# Purpose: connect a LangChain v1 agent to both the local and the DeepWiki MCP servers.
import asyncio
from langchain.agents import create_agent
from langchain_ollama import ChatOllama
from langchain_mcp_adapters.client import MultiServerMCPClient
# Local Ollama model to use for the chat agent.
CHAT_MODEL = "qwen3.5:4b"
# Hosted remote MCP server we'll connect to over HTTP.
DEEPWIKI_MCP_URL = "https://mcp.deepwiki.com/mcp"
# System prompt that tells the model what tools it has and how to behave.
SYSTEM_PROMPT = (
"You are a helpful assistant with access to tools for checking the current time, "
"counting words, and looking up information about GitHub repositories. "
"Use tools when the user's request needs information you don't already have. "
"If a tool returns an error, tell the user plainly and do not retry with made-up arguments. "
"If the question doesn't need a tool, just answer directly."
)
async def build_agent(client: MultiServerMCPClient):
# Load tools from all connected MCP servers.
# This is async because MCP communication happens over I/O.
tools = await client.get_tools()
print(f"Loaded {len(tools)} tools: {[t.name for t in tools]}")
# Create the local Ollama chat model.
model = ChatOllama(model=CHAT_MODEL, temperature=0)
# Build a LangChain agent with the local model and all MCP tools.
return create_agent(
model=model,
tools=tools,
system_prompt=SYSTEM_PROMPT,
)
async def main():
# Create one MCP client that connects to two servers:
#
# 1. "tools" is a local MCP server started as a subprocess over stdio.
# LangChain will launch `python mcp_server.py` for us.
# 2. "deepwiki" is a hosted MCP server we connect to over HTTP.
client = MultiServerMCPClient({
"tools": {
"command": "python",
"args": ["mcp_server.py"],
"transport": "stdio",
},
"deepwiki": {
"url": DEEPWIKI_MCP_URL,
"transport": "streamable_http",
},
})
# Build the agent after the MCP client is ready and tools are loaded.
agent = await build_agent(client)
print("\nReady! Ask the agent something.")
print("Type 'exit' to quit.\n")
while True:
question = input("You: ").strip()
if not question or question.lower() in {"exit", "quit"}:
break
# Send the user's message to the agent.
# We use `ainvoke()` because the agent may call async MCP tools.
result = await agent.ainvoke({
"messages": [{"role": "user", "content": question}],
})
# Walk through the returned messages and print any tool calls
# the agent made during this turn.
for msg in result["messages"]:
tool_calls = getattr(msg, "tool_calls", None)
if tool_calls:
for call in tool_calls:
print(f"[tool call] {call['name']}({call['args']})")
# The final message in the list is the agent's final answer.
print(f"\nAnswer: {result['messages'][-1].content}\n")
if __name__ == "__main__":
# Run the async program.
asyncio.run(main())
Why build_agent is declared async and called with await: client.get_tools() has to actually start the local subprocess and open the HTTP connection to DeepWiki before it can return a tool list, both of which are I/O operations. Without await, you would get back an unfinished coroutine object instead of the agent you actually want to use.
Step 5: Run the agent and watch it choose tools
# Context: mcp_server.py and agent_with_mcp.py both saved, venv activated.
# Purpose: launch the agent, which starts the local MCP server itself.
python3 agent_with_mcp.py
You do not need to start mcp_server.py yourself first: MultiServerMCPClient launches it as a subprocess automatically, using the exact command and args you configured in Step 4, and separately opens an HTTP connection to DeepWiki. If either server is unreachable, you will see an error during this startup phase rather than a silent failure later.
Expected output once startup succeeds:
Starting MCP server 'local-tools' with transport 'stdio'
Loaded 5 tools: ['current_time', 'word_count', 'read_wiki_structure', 'read_wiki_contents', 'ask_question']
Ready! Ask the agent something.
Type 'exit' to quit.
Five tools confirms both connections worked: two from your local server, three from DeepWiki. Now ask it something that only a local tool can answer:
You: what is the current time
[tool call] current_time({})
Answer: The current time is 2026-07-01 16:41:42
And something that requires the remote DeepWiki tools, naming any public GitHub repository you like:
You: Give me one line summary of karpathy/nanochat
[tool call] ask_question({'repoName': 'karpathy/nanochat', 'question': 'Give me a one-line summary of this repository'})
Answer: This repository, karpathy/nanochat, is a minimal, full-stack experimental
system for training large language models (LLMs) from scratch, designed to be
accessible and cost-effective...
Watch the [tool call] line on every turn, not just the final answer. It tells you exactly which tool the model picked and with what arguments, which is how you catch a model reaching for the wrong tool, or the right tool with a malformed argument, before you learn to trust its answers blindly. Local models are smaller than hosted frontier models and can be more prone to picking a plausible-looking tool over the correct one: spot-checking the tool-call line is how you catch that early rather than after you have already trusted a wrong answer.
Common mistakes and how to catch them
- Running the agent from a different terminal than the one where you activated the venv.
MultiServerMCPClientlaunches your local server with the literal commandpython mcp_server.py, using whateverpythonresolves to in the environment the agent process inherits. If you activate the venv in one terminal but launchagent_with_mcp.pyfrom a different, non-activated shell, the subprocess’spythonwill not have FastMCP installed, and you will see aModuleNotFoundErrorcoming from the subprocess. - An older LangChain already on your system taking priority.
create_agentis a LangChain v1 addition. If you skip the virtual environment and a pre-1.0 LangChain is already installed globally, you can hit an import error or a confusing signature mismatch. Runpip show langchaininside the activated venv and confirm the version starts with1.or higher before debugging anything else. - Renaming
mcp_server.pywithout updating the agent’sargslist. The filename you save in Step 3 and the string inside"args": ["mcp_server.py"]in Step 4 have to match exactly, because that string is a literal command-line argument passed topython. Rename one without the other and the agent fails at startup with a file-not-found error from the subprocess, not from your own code. - Ollama not running, or the model not actually pulled yet. If
ChatOllamacannot reach the Ollama service, you will get a connection error the moment the agent tries to generate a response. Re-runollama listto confirm the model tag you configured inCHAT_MODELis present, and confirm the service itself is active (the Linux installer sets it up to start automatically;ollama servestarts it manually in the foreground if needed). - No outbound access to
mcp.deepwiki.com. A restrictive firewall or offline network will not stop your local tools (current_time,word_count) from working, since those never leave your machine, but any question routed to a DeepWiki tool will fail or hang. This is actually a useful way to confirm the local/remote boundary is real: block outbound HTTPS and watch local-only questions keep working while DeepWiki questions stop. - Thin docstrings on your own
@mcp.toolfunctions. Since FastMCP builds the tool’s description for the model directly from your docstring, a one-line, vague docstring gives the model very little to decide with, and it will more often skip your tool or call it incorrectly. Write the docstring as if you were explaining to a new teammate exactly when to reach for this function.
Verifying it works end-to-end
- Run
ollama listand confirm your chosen Qwen 3.5 tag is present. - Run
python3 mcp_server.pydirectly and confirm it prints its stdio startup line with no traceback, then stop it withCtrl+C. - Run
python3 agent_with_mcp.pyand confirm the startup line readsLoaded 5 tools, naming both of your local tools and all three DeepWiki tools. - Ask a local-only question (for example, “what time is it”) and confirm the
[tool call]line namescurrent_time, not a DeepWiki tool. - Ask about a public GitHub repository (for example, “summarize <owner>/<repo>” for any repo you know) and confirm the
[tool call]line names one of the DeepWiki tools with a sensiblerepoNameargument. - As a negative test, disconnect from the network (or block outbound access to
mcp.deepwiki.com) and confirm local-tool questions still work while DeepWiki questions now fail or hang: proof the local/remote split is real and not just a label in the code.
Next steps
Once this baseline works, the natural extensions are to add more of your own functions to mcp_server.py, each decorated with @mcp.tool: they show up automatically the next time the agent loads tools, with no changes needed on the agent side at all. From there, look at the official MCP documentation for the growing registry of other public MCP servers you can connect to alongside DeepWiki, and at FastMCP’s own documentation for how to deploy your server over HTTP instead of stdio once you want more than one agent, or more than one machine, sharing the same tools. If you go that route, remember that the two tools built in this tutorial are harmless and read-only by design: a tool that can write files, hit an internal API, or spend money needs the same access-control thinking you would give any other network-facing service before you expose it beyond your own machine.
Sources
- freeCodeCamp: How to Build an MCP Server with FastMCP for Your Local AI Agent
- Model Context Protocol: What is the Model Context Protocol (MCP)?
- FastMCP Documentation: Welcome to FastMCP
- FastMCP Documentation: Installation
- LangChain AI: langchain-mcp-adapters GitHub Repository
- Cognition / Devin Docs: DeepWiki MCP Server
- Ollama: Download Ollama on Linux
- Ollama Library: qwen3.5
- Featured image source: USB Type-C Cable, Wikimedia Commons
Image credit: Tony Webster, CC BY 2.0 via Wikimedia Commons; cropped, resized, and converted to WebP for sxz.io.








No Comment! Be the first one.