A simple, dependency-free way to create custom AI tools in Python and host the Model Context Protocol (MCP) server.
This server allows you to expose Python functions as tools to AI models (like Claude Desktop, LM Studio, etc.) using a simple decorator (@mcp_tool). It supports both SSE (Server-Sent Events) for standard clients and Direct HTTP for stateless clients.
- Decorator-based: Register tools with a simple
@mcp_tooldecorator. - Auto-Documentation: Automatically extracts descriptions from standard Docstrings or
Annotatedtype hints. - Universal Mode: Works with both streaming clients (Claude) and stateless clients (LM Studio).
- No Heavy SDKs: Built with Starlette and Uvicorn.
- Clone the repository:
git clone https://github.com/yourusername/python-mcp-server.git cd python-mcp-server - Install dependencies:
pip install -r requirements.txt
Run the server on port 8000:
python main.py- Open LM Studio.
- Go to the MCP tab (the plug icon).
- Create a new server config (or edit
mcp.json):{ "mcpServers": { "python-tools": { "url": "http://localhost:8000/sse" } } } - Connect. The status should turn Green.
- Edit your Claude config file:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
- macOS:
- Add the configuration:
{ "mcpServers": { "python-tools": { "command": "uv", "args": [ "run", "--with", "uvicorn", "--with", "starlette", "--with", "sse-starlette", "python", "c:\\path\\to\\python-mcp-server\\main.py" ] } } }
To add a new tool, simply create a new Python file in the tools/ folder (e.g., tools/weather.py). The server automatically scans this folder on startup.
You can document your tools in two ways. The server will convert these definitions into the JSON format required by the AI.
Best for clean, readable code. The parser automatically extracts parameter descriptions from the Args: block.
from registry import mcp_tool
@mcp_tool
def get_weather(location: str, days: int = 3):
"""
Get the weather forecast for a specific city.
Args:
location: The city to search for (e.g. Paris, London).
days: Number of days to forecast (1-7).
"""
return f"Weather in {location} for {days} days: Sunny, 25C"Best for keeping the description right next to the parameter.
from typing import Annotated
from registry import mcp_tool
@mcp_tool
def calculate_bmi(
weight: Annotated[float, "Weight in Kilograms"],
height: Annotated[float, "Height in Meters"]
):
"""Calculates Body Mass Index."""
return weight / (height ** 2)When the server runs, it converts the Python functions above into this JSON Schema. This is what is sent to the AI so it knows how to call your tools.
{
"name": "get_weather",
"description": "Get the weather forecast for a specific city.",
"inputSchema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city to search for (e.g. Paris, London)."
},
"days": {
"type": "integer",
"description": "Number of days to forecast (1-7).",
"default": 3
}
},
"required": [
"location"
]
}
}Note: Since days had a default value (= 3) in Python, it is marked as Optional in the JSON schema automatically.
MIT License