To build an MCP server in Python, install the official SDK, decorate plain functions as tools, and run the file over stdio so a host launches it as a subprocess. For a terminal experience, add a small client that prompts you in a loop. The one rule to remember: never print to stdout.
What do you need to start?
The official Python SDK lists Python 3.11 or newer and installs with pip install "mcp[cli]". The cli extra adds the mcp command with dev, run and install subcommands. The README on GitHub also shows uv as an option. Check that page for the current API, because the SDK has a version 2 line and the examples there are what this post follows.
A complete server, taken from the README, is a handful of lines:
from mcp.server import MCPServer
mcp = MCPServer("Demo")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
The type hints become the tool schema and the docstring becomes the description the model sees. You write no request parsing or protocol code. The README adds a templated resource in the same style.
How do you try it from the terminal?
Run uv run mcp dev server.py to open the MCP Inspector, call add with two numbers and confirm you get the sum back. To serve the same file over HTTP, the README gives uv run mcp run server.py with the streamable HTTP transport option.
Then write a client that behaves like a tiny shell. The README shows a Client that takes a URL and can call a tool by name. An interactive loop around it looks like this:
import asyncio
from mcp import Client
async def main() -> None:
async with Client("http://localhost:8000/mcp") as client:
while True:
line = input("add a b> ").strip()
if line in ("q", "quit"):
break
a, b = line.split()
result = await client.call_tool("add", {"a": int(a), "b": int(b)})
print(result.structured_content)
asyncio.run(main())
It blocks on input, which is fine for a demo. A real tool would use an async prompt library so the event loop keeps running, and would handle bad input instead of crashing on the split.
Why does printing to stdout break a stdio server?
Because stdout is the protocol. In the stdio transport the host launches your server as a subprocess, and the specification says the server reads JSON-RPC messages from standard input and writes them to standard output. Messages are newline delimited, and the server must not write anything to stdout that is not a valid MCP message. Logging belongs on stderr.
Constructed example: you add print("starting up") to debug. The host reads that line as a protocol message, fails to parse it, and drops the connection. The error you see is about JSON, and nothing points to your print statement. If your terminal tool seems to hang or disconnect right after launch, check for stray prints first.
What changes when you serve over HTTP?
The MCP specification gives three security rules for the streamable HTTP transport. Validate the Origin header on all incoming connections to prevent DNS rebinding. When running locally, bind to 127.0.0.1 rather than 0.0.0.0. Implement authentication for all connections. Skipping them means a web page in your browser could, in principle, talk to a server on your machine.
How to check or apply this
- Create a virtual environment and install the SDK. Confirm the Python version.
- Write one tool with type hints and a clear docstring.
- Run it under the Inspector and call it with good and bad input.
- Search your code for print calls and replace them with logging to stderr.
- If you move to HTTP, bind to localhost, validate Origin and require authentication before anything else.
- Treat tool arguments as untrusted input, especially anything that reaches a shell, file path or database.
If you connect servers like this to agents at work, policy checks on tool calls matter. Safeguard offers an MCP Guardrail for that purpose, but the habits above come first.
What should a real tool add?
A demo that adds two numbers teaches the shape, but real servers do things with consequences. Give each tool a narrow purpose, validate every argument, return clear errors and keep side effects to a minimum. Prefer read only tools until you have a reason to write. Add a timeout to anything that calls out, and avoid returning secrets in results, because tool output goes back into a model's context. Test with hostile input, such as very long strings and path separators, before you let an agent call the tool.
Quick check
Which transport should a local terminal tool use?
Stdio is the simplest. The client launches your server as a subprocess, and the specification recommends clients support it whenever possible.
Why does my server disconnect right after launch?
Look for anything written to stdout that is not a valid MCP message, such as a debug print. Send logs to stderr instead.
Sources
Last reviewed: 18 October 2026.