This Model Context Protocol (MCP) tutorial builds a working MCP server that exposes a tool to any MCP client, such as Claude Desktop. Using the…
Recap what MCP is so the build makes sense to a cold reader.
The Model Context Protocol (MCP) is an open standard, introduced by Anthropic in 2024, that lets AI applications reach external tools and data through one uniform interface. You expose capabilities by writing an MCP server; any MCP client — Claude Desktop, an IDE, or your own app — can then discover and call them.
So building an MCP server is how you give an AI application a new ability. Write the tool once as a server, and every MCP-compatible client can use it without custom glue code.
Follow the numbered steps to publish a Python function as an MCP tool.
Building a server takes four steps. (1) Install the SDK with pip install "mcp[cli]". (2) Create a FastMCP server object with a name. (3) Decorate each function you want to expose with @mcp.tool(), using type hints and a docstring — the client reads these to know the arguments and purpose. (4) Run the server over stdio.
The type hints become the tool's JSON Schema and the docstring becomes its description, so a clear signature and docstring are what make the tool usable by the model.
from mcp.server.fastmcp import FastMCP mcp = FastMCP("demo") @mcp.tool() def add(a: int, b: int) -> int: """Add two numbers and return the sum.""" return a + b if __name__ == "__main__": mcp.run() # serve over stdio
This is a full, runnable MCP server. FastMCP registers add as a tool; the int type hints define its arguments and the docstring describes it. mcp.run() serves over stdio, so a client can launch this file and call add.
Register the server so an MCP client like Claude Desktop can use its tools.
To use the server, add it to your client's MCP configuration by naming it and giving the command to launch it. For a local stdio server that is the interpreter plus the script path. The client starts the server as a subprocess, performs the MCP handshake, and discovers its tools automatically.
After restarting the client, the tool should appear in its tool list. Ask the model to use it — 'add 2 and 3' — and confirm it calls your function rather than answering from memory.
{
"mcpServers": {
"demo": {
"command": "python",
"args": ["/abs/path/to/server.py"]
}
}
}Name the server and tell the client how to launch it. Use an absolute path so the client can find the script regardless of its working directory. On restart, the client runs this command, connects, and lists the add tool.
Avoid the errors that keep a server from connecting or its tools from being used well.
The frequent problems: using a relative script path (the client cannot find it — always use an absolute path); missing type hints or docstrings (the model gets a vague schema and calls the tool wrong or not at all); and printing to standard output in a stdio server, which corrupts the protocol stream — log to standard error instead.
Also keep tools focused and well-named, validate arguments before acting, and never expose a tool that takes an irreversible action without a confirmation step, since a model can be steered by injected content.
This MCP tutorial builds a server that publishes a tool to any MCP client. Install the SDK, create a FastMCP server, decorate a type-hinted, documented function with @mcp.tool(), and run it over stdio. Register the server in the client's config with an absolute launch command, restart, and confirm the tool is called. Avoid relative paths, missing hints/docstrings, and printing to stdout, which corrupts the stream.
Extend the demo server with a second tool — say get_time() or a lookup that reads a local file. Give it type hints and a docstring, register the server in a client, and verify the model calls your new tool instead of answering from memory.
How do you expose a Python function as an MCP tool with FastMCP?
FastMCP turns a decorated, type-hinted, documented function into a tool; the hints become its schema and the docstring its description.
How does an MCP client connect to a local stdio server?
A config entry names the server and the command to launch it; the client runs that command, performs the MCP handshake, and lists the tools.
Why must type hints and docstrings be present on an MCP tool?
The client derives the argument schema from type hints and the tool's purpose from the docstring, so vague signatures lead to wrong or missing calls.
What is a common mistake when writing a stdio MCP server?
In stdio mode the protocol uses standard output, so stray prints break it; logging to stderr and using absolute paths avoid the usual connection failures.