https://github.com/mrexodia/zeromcp
Zero-dependency MCP server implementation.
https://github.com/mrexodia/zeromcp
mcp mcp-sdk modelcontextprotocol python python-mcp
Last synced: 11 days ago
JSON representation
Zero-dependency MCP server implementation.
- Host: GitHub
- URL: https://github.com/mrexodia/zeromcp
- Owner: mrexodia
- License: mit
- Created: 2025-11-15T22:49:35.000Z (4 months ago)
- Default Branch: master
- Last Pushed: 2026-02-23T01:13:01.000Z (15 days ago)
- Last Synced: 2026-02-23T07:27:05.917Z (15 days ago)
- Topics: mcp, mcp-sdk, modelcontextprotocol, python, python-mcp
- Language: Python
- Homepage: https://pypi.org/project/zeromcp/
- Size: 194 KB
- Stars: 59
- Watchers: 0
- Forks: 1
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# zeromcp
**Minimal MCP server implementation in pure Python.**
A lightweight, handcrafted implementation of the [Model Context Protocol](https://modelcontextprotocol.io/) focused on what most users actually need: exposing tools with clean Python type annotations.
## Features
- ✨ **Zero dependencies** - Pure Python, standard library only
- 🎯 **Type-safe** - Native Python type annotations for everything
- 🚀 **Fast** - Minimal overhead, maximum performance
- 🛠️ **Handcrafted** - Written by a human[1](#ai-usage), verified against the spec
- 🌐 **HTTP/SSE transport** - Streamable responses
- 📡 **Stdio transport** - For legacy clients
- 📦 **Tiny** - Less than 1,000 lines of code
## Installation
```bash
pip install zeromcp
```
Or with uv:
```bash
uv add zeromcp
```
## Quick Start
```python
from typing import Annotated
from zeromcp import McpServer
mcp = McpServer("my-server")
@mcp.tool
def greet(
name: Annotated[str, "Name to greet"],
age: Annotated[int | None, "Age of person"] = None
) -> str:
"""Generate a greeting message"""
if age:
return f"Hello, {name}! You are {age} years old."
return f"Hello, {name}!"
if __name__ == "__main__":
mcp.serve("127.0.0.1", 8000)
```
Then manually test your MCP server with the [inspector](https://github.com/modelcontextprotocol/inspector):
```bash
npx -y @modelcontextprotocol/inspector
```
Once things are working you can configure the `mcp.json`:
```json
{
"mcpServers": {
"my-server": {
"type": "http",
"url": "http://127.0.0.1:8000/mcp"
}
}
}
```
## Stdio Transport
For MCP clients that only support stdio transport:
```python
from zeromcp import McpServer
mcp = McpServer("my-server")
@mcp.tool
def greet(name: str) -> str:
"""Generate a greeting"""
return f"Hello, {name}!"
if __name__ == "__main__":
mcp.stdio()
```
Then configure in `mcp.json` (different for every client):
```json
{
"mcpServers": {
"my-server": {
"command": "python",
"args": ["path/to/server.py"]
}
}
}
```
## Type Annotations
zeromcp uses native Python `Annotated` types for schema generation:
```python
from typing import Annotated, Optional, TypedDict, NotRequired
class GreetingResponse(TypedDict):
message: Annotated[str, "Greeting message"]
name: Annotated[str, "Name that was greeted"]
age: Annotated[NotRequired[int], "Age if provided"]
@mcp.tool
def greet(
name: Annotated[str, "Name to greet"],
age: Annotated[Optional[int], "Age of person"] = None
) -> GreetingResponse:
"""Generate a greeting message"""
if age is not None:
return {
"message": f"Hello, {name}! You are {age} years old.",
"name": name,
"age": age
}
return {
"message": f"Hello, {name}!",
"name": name
}
```
## Union Types
Tools can accept multiple input types:
```python
from typing import Annotated, TypedDict
class StructInfo(TypedDict):
name: Annotated[str, "Structure name"]
size: Annotated[int, "Structure size in bytes"]
fields: Annotated[list[str], "List of field names"]
@mcp.tool
def struct_get(
names: Annotated[list[str], "Array of structure names"]
| Annotated[str, "Single structure name"]
) -> list[StructInfo]:
"""Retrieve structure information by names"""
return [
{
"name": name,
"size": 128,
"fields": ["field1", "field2", "field3"]
}
for name in (names if isinstance(names, list) else [names])
]
```
## Error Handling
```python
from zeromcp import McpToolError
@mcp.tool
def divide(
numerator: Annotated[float, "Numerator"],
denominator: Annotated[float, "Denominator"]
) -> float:
"""Divide two numbers"""
if denominator == 0:
raise McpToolError("Division by zero")
return numerator / denominator
```
## Resources
Expose read-only data via URI patterns. Resources are serialized as JSON.
```python
from typing import Annotated
@mcp.resource("file://data.txt")
def read_file() -> dict:
"""Get information about data.txt"""
return {"name": "data.txt", "size": 1024}
@mcp.resource("file://{filename}")
def read_any_file(
filename: Annotated[str, "Name of file to read"]
) -> dict:
"""Get information about any file"""
return {"name": filename, "size": 2048}
```
## Prompts
Expose reusable prompt templates with typed arguments.
```python
from typing import Annotated
@mcp.prompt
def code_review(
code: Annotated[str, "Code to review"],
language: Annotated[str, "Programming language"] = "python"
) -> str:
"""Review code for bugs and improvements"""
return f"Please review this {language} code:\n\n```{language}\n{code}\n```"
```
## CORS
By default, zeromcp allows CORS requests from localhost origins (`localhost`, `127.0.0.1`, `::1`) on **any port**. This allows tools like the MCP Inspector or local AI tools to communicate with your MCP server.
```python
from zeromcp import McpServer
mcp = McpServer("my-server")
# Default: allow localhost on any port
mcp.cors_allowed_origins = mcp.cors_localhost
# Allow all origins (use with caution)
mcp.cors_allowed_origins = "*"
# Allow specific origins
mcp.cors_allowed_origins = [
"http://localhost:3000",
"https://myapp.example.com",
]
# Disable CORS (blocks all browser cross-origin requests)
mcp.cors_allowed_origins = None
# Custom logic
mcp.cors_allowed_origins = lambda origin: origin.endswith(".example.com")
```
Note: CORS only affects browser-based requests. Non-browser clients like `curl` or MCP desktop apps are unaffected by this setting.
## Supported clients
The following clients have been tested:
- [Claude Code](https://code.claude.com/docs/en/mcp#installing-mcp-servers)
- [Claude Desktop](https://modelcontextprotocol.io/docs/develop/connect-local-servers#installing-the-filesystem-server) (_stdio only_)
- [Visual Studio Code](https://code.visualstudio.com/docs/copilot/customization/mcp-servers)
- [Roo Code](https://docs.roocode.com/features/mcp/using-mcp-in-roo) / [Cline](https://docs.cline.bot/mcp/configuring-mcp-servers) / [Kilo Code](https://kilocode.ai/docs/features/mcp/using-mcp-in-kilo-code)
- [LM Studio](https://lmstudio.ai/docs/app/mcp)
- [Jan](https://www.jan.ai/docs/desktop/mcp#configure-and-use-mcps-within-jan)
- [Gemini CLI](https://geminicli.com/docs/tools/mcp-server/#how-to-set-up-your-mcp-server)
- [Cursor](https://cursor.com/docs/context/mcp)
- [Windsurf](https://docs.windsurf.com/windsurf/cascade/mcp)
- [Zed](https://zed.dev/docs/ai/mcp) (_stdio only_)
- [Warp](https://docs.warp.dev/knowledge-and-collaboration/mcp#adding-an-mcp-server)
_Note_: generally the `/mcp` endpoint is preferred, but not all clients support it correctly.