How to Build an MCP Server in Python from Scratch
Quick answer
An MCP server is a small program that exposes tools, data and prompts to an AI assistant through the Model Context Protocol, so the assistant can call your code instead of guessing. In Python, the fastest route is a FastMCP server: you write ordinary functions, mark each one with a decorator, and run the server. The assistant discovers the tools, calls them with arguments, and uses the results in its answer.
What an MCP server actually is
A host application runs the assistant. A client keeps the connection to a server. The server is your code, and it exposes only what you define. That three-part split is the whole idea: the assistant never reads your database, your files or your internal API directly. It asks the server, the server runs a function you wrote, and the result comes back in a structured form the assistant can use.
This matters because it separates permission from prompting. A prompt can ask for anything, but a server only exposes the tools you registered. If a tool is not registered, the assistant cannot call it, no matter how the user phrases the request. That is why MCP servers are useful for coding assistants, internal tools and data lookups: the boundary is code, not language.
A server does not replace an API. An API is usually called by your own code, with keys and endpoints you control directly. An MCP server is called by an assistant on a user's behalf, through a client that negotiates capabilities first. Use an API when your program calls a service. Use an MCP server when an assistant should be able to call your program's capabilities while it works.
What a server can expose: tools, resources and prompts
Tools are functions the assistant can call, and they are the part you will use most. A tool takes named arguments, validates them, runs, and returns a result. Good tools do one thing, have a clear name, and describe their arguments in plain language, because the assistant chooses tools from those descriptions.
Resources are read-only data the assistant can fetch, such as a document, a configuration file or a report. Prompts are reusable instruction templates stored on the server, so a team can share a workflow instead of pasting the same instructions into every conversation. Start with tools. Add resources when the assistant needs to read something repeatedly, and prompts when a multi-step workflow is worth standardising.
Keep the surface small at first. Three well-named tools that work are better than twenty vague ones. Every extra tool is another choice the assistant has to make, and vague names lead to wrong calls. Name tools by what they do, not by internal system names: search_notes, not query_v2.
Before you start
You need Python 3.10 or newer and a way to install packages. Create a project folder and a virtual environment, then install a Python MCP SDK. The example below uses FastMCP, a common Python approach for building MCP servers with decorators. Check the documentation of the exact version you install before running the code, because APIs can change between versions. The example is illustrative: it shows the structure of a complete server, and you should adapt imports and options to the SDK version you install.
Decide two things before writing code. First, what data the server may touch, and what it must never touch. Write that list down; it becomes your tool list. Second, how the server will run: locally over standard input and output for personal tools, or over HTTP for a shared service. Local first is the right default. A remote server is a network service, and it needs authentication, validation and logging before anyone else can reach it.
A complete example: a notes server, from scratch
This server keeps short notes in memory and exposes three tools: add a note, list notes, and search notes by keyword. It has no database, no API key and no external service, which keeps the example focused on the MCP parts. In a real project you would replace the in-memory list with your own storage, but the tool definitions would look the same.
from fastmcp import FastMCP
mcp = FastMCP("notes-server")
notes: list[str] = []
@mcp.tool()
def add_note(text: str) -> str:
"""Save a short note and return a confirmation."""
notes.append(text)
return f"Saved note {len(notes)}."
@mcp.tool()
def list_notes() -> list[str]:
"""Return every saved note, in the order it was added."""
return notes
@mcp.tool()
def search_notes(keyword: str) -> list[str]:
"""Return the notes that contain the keyword."""
return [n for n in notes if keyword.lower() in n.lower()]
if __name__ == "__main__":
mcp.run()
Read the code in order. The server object gets a name. Each function gets a decorator that turns it into a tool, and a docstring that tells the assistant what the tool does. Type hints describe the arguments, so the client can validate a call before it runs. The run call at the bottom starts the server. Nothing else is needed for a first working server.
Two details deserve attention. The docstring is not decoration: it is how the assistant decides whether to call this tool, so write it for the assistant, not for yourself. And the function should validate its inputs and fail with a clear message, because a tool that crashes silently produces a confusing answer downstream.
Run the file from your project folder. If it starts without an error, the server is ready to connect to a client. If it fails at import time, the usual cause is the package name or version: check what you installed and what the SDK documentation for that version says to import.
Testing it: client, Inspector and a Python client
The fastest test is the MCP Inspector, a testing tool that lists a server's tools and lets you call them by hand. Point it at your server, confirm the three tools appear, and call search_notes with a keyword you saved. If the tools do not appear, the problem is in the server definition, not in the assistant, and the Inspector output will usually say where.
Next, connect a real assistant client. In a client configuration file, a local server is usually described by the command that starts it and its working directory, for example the Python interpreter, your server file, and the project folder. Keep the command absolute or run it from the project folder, because relative paths are the most common reason a client cannot start a local server.
You can also call the server from Python with an MCP client. The pattern is the same in every SDK: open a session to the server, list its tools, then call one by name with arguments. A short client script is worth writing even if you plan to use an assistant client, because it gives you a repeatable test that does not depend on a prompt.
# Illustrative client pattern: adapt imports to your SDK version.
# 1. Connect to the notes server.
# 2. List tools and confirm add_note, list_notes, search_notes appear.
# 3. Call add_note with a test note, then search_notes for one word in it.
# 4. Compare the returned notes with what you saved.Remote servers and deployment
A remote MCP server runs over HTTP instead of a local subprocess, so a team or a hosted assistant can reach it. The code changes little: the same tools are exposed, but the transport changes, and the server now needs everything a small web service needs. That is the part people underestimate.
Before exposing a remote server, add authentication so only approved clients can connect, validate every argument as if it came from an untrusted caller, log tool calls so you can audit them, and limit what each tool can do. A tool that reads files should read only an approved folder. A tool that writes should write only through a checked path. Never expose a server with database credentials, file-system access or write permissions and no authentication, even for a quick test.
For deployment, run the server as a normal service behind your existing web hosting, keep it on a private network when only your own tools need it, and version the tool list: renaming or removing a tool breaks clients that already use it, so treat tool names as a public interface.
Common errors, and what to check first
When something fails, check in this order. First, does the server start on its own, without a client? If not, the error is in your code or your installation. Second, does the client list the tools? If not, the connection or the startup command is wrong, usually a path or an environment issue. Third, does a tool call fail? Then the arguments, the validation or the data the tool touches are the problem, and the tool's error message is the clue.
Other frequent mistakes: mixing up local and remote transports, so the client tries HTTP against a local server or the reverse; forgetting that a local server inherits the client's environment, so a missing environment variable breaks it only inside the client; and registering tools with vague names or missing descriptions, so the assistant calls the wrong one. Fix names and descriptions first, before adding more tools.
Finally, keep the server boring. One responsibility per tool, clear errors, no hidden side effects, and a test call after every change. That discipline is what makes an MCP server reliable enough for an assistant to use every day.
Frequently asked questions
What is a Python MCP server example?
A small program that exposes functions as tools an assistant can call. The notes server above is a complete example: three tools, add_note, list_notes and search_notes, defined with decorators and started with a single run call.
How do I build an MCP server in Python from scratch?
Create a project folder, install a Python MCP SDK such as FastMCP, define one tool at a time with a clear name, description and type hints, run the server locally, and test it with the MCP Inspector before connecting an assistant client.
What is a remote MCP server?
An MCP server that runs over HTTP so clients on other machines can reach it. It exposes the same tools as a local server, but it needs authentication, input validation, logging and permission limits, because it is a network service.
How do I test an MCP server?
Start it on its own first, then list its tools in the MCP Inspector and call one by hand. After that, connect an assistant client and try a real prompt, and keep a small client script as a repeatable test.
Why does my client not list my tools?
Usually the startup command, working directory or environment is wrong, or the server failed to start. Run the server by hand first, check the command the client uses, and confirm the tool definitions load without an error.