Skip to main content

Zero-config MCP server. Drop a .py file in a folder, it's a tool.

Project description

ZeroMCP — Python

Drop a .py file in a folder, get a sandboxed MCP server. Stdio out of the box, zero dependencies.

Getting started

# tools/hello.py — this is a complete MCP server
tool = {
    "description": "Say hello to someone",
    "input": {"name": "string"},
}

async def execute(args, ctx):
    return f"Hello, {args['name']}!"
python3 -m zeromcp serve ./tools

That's it. Stdio transport works immediately. Drop another .py file to add another tool. Delete a file to remove one. No server object, no decorators, no main block.

vs. the official SDK

The official Python SDK (FastMCP) requires a server object, decorators, and a __main__ block. Adding a tool means editing server code and restarting. ZeroMCP is file-based — each tool is its own file, discovered automatically.

In benchmarks, ZeroMCP Python handles 12,936 requests/second over stdio versus the official SDK's 1,018 — 12.7x faster with 59% less memory. Over HTTP (Starlette), ZeroMCP serves 2,623 rps at 27 MB versus the official SDK's 635 rps at 80-87 MB. ZeroMCP uses only the standard library. The official SDK pulls in pydantic, httpx, uvicorn, and starlette just for stdio.

Python passes all 10 conformance suites and survives 21/22 chaos monkey attacks.

The official SDK has no sandbox. ZeroMCP enforces per-tool network allowlists, credential isolation, and filesystem controls at runtime.

HTTP / Streamable HTTP

ZeroMCP doesn't own the HTTP layer. You bring your own framework; ZeroMCP gives you an async handler that takes a JSON-RPC dict and returns a response dict (or None for notifications).

from zeromcp import create_handler

handler = await create_handler("./tools")
# handler(request: dict) -> dict | None

Flask

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route("/mcp", methods=["POST"])
async def mcp():
    response = await handler(request.get_json())
    if response is None:
        return "", 204
    return jsonify(response)

FastAPI

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI()

@app.post("/mcp")
async def mcp(req: Request):
    response = await handler(await req.json())
    if response is None:
        return JSONResponse(status_code=204, content=None)
    return response

Requirements

  • Python 3.10+
  • No external dependencies (stdlib only)

Install

pip install -e .

Defining tools

# tools/add.py
tool = {
    "description": "Add two numbers together",
    "input": {"a": "number", "b": "number"},
}

async def execute(args, ctx):
    return {"sum": args["a"] + args["b"]}

Input types

Shorthand strings: "string", "number", "boolean", "object", "array".

Returning values

Return a string or a dict. ZeroMCP wraps it in the MCP content envelope for you.

Sandbox

The Python implementation has full runtime sandboxing.

Network allowlists

tool = {
    "description": "Fetch from our API",
    "input": {"endpoint": "string"},
    "permissions": {
        "network": ["api.example.com", "*.internal.dev"],
    },
}

async def execute(args, ctx):
    res = await ctx.fetch(f"https://api.example.com/{args['endpoint']}")
    return res["body"]

ctx.fetch validates the hostname against the allowlist. Unlisted domains are blocked and logged.

Credential injection

Tools receive secrets via ctx.credentials, configured per namespace. Tools never call os.environ directly.

Filesystem and exec control

Tools must declare fs: 'read' or fs: 'write' for filesystem access. Static auditing and proxy objects enforce the restrictions.

Directory structure

Tools are discovered recursively. Subdirectory names become namespace prefixes:

tools/
  hello.py          -> tool "hello"
  math/
    add.py          -> tool "math_add"

Configuration

Optional zeromcp.config.json in the working directory. See the root README for the full schema.

Testing

python3 -m pytest

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

antidrift_zeromcp-0.1.0.tar.gz (12.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

antidrift_zeromcp-0.1.0-py3-none-any.whl (13.4 kB view details)

Uploaded Python 3

File details

Details for the file antidrift_zeromcp-0.1.0.tar.gz.

File metadata

  • Download URL: antidrift_zeromcp-0.1.0.tar.gz
  • Upload date:
  • Size: 12.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for antidrift_zeromcp-0.1.0.tar.gz
Algorithm Hash digest
SHA256 0292a4d0e15ecd26a14aaa09019adda49e8da8f8f020efe1aed747e5b97387d2
MD5 6cf9ed36e43ce89001a761a7b7f728e4
BLAKE2b-256 0ea561ffde979604b302a5190e6a2fc8529e2bc2cb13c90e5aa2eb25e5412566

See more details on using hashes here.

Provenance

The following attestation bundles were made for antidrift_zeromcp-0.1.0.tar.gz:

Publisher: publish-python.yml on antidrift-dev/zeromcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file antidrift_zeromcp-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for antidrift_zeromcp-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3d24d6588cb6d6d8f56e0cc565fc89b0709c0a2e203e8ea61d4348ed8e5e38bb
MD5 8c74db5853bc5391217842179d07ec81
BLAKE2b-256 219635efd7debf61ae638b5f82a921e188b6114e37515a421bb809d234e14a90

See more details on using hashes here.

Provenance

The following attestation bundles were made for antidrift_zeromcp-0.1.0-py3-none-any.whl:

Publisher: publish-python.yml on antidrift-dev/zeromcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page