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.2.0.tar.gz (22.5 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.2.0-py3-none-any.whl (16.9 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for antidrift_zeromcp-0.2.0.tar.gz
Algorithm Hash digest
SHA256 c2e12289210bb9975e62b0f547922f33711894c5d8208d1bef523e688c73c15b
MD5 fb6baff32bd7721a5847b1c8e3bfbe6c
BLAKE2b-256 c00691cb45a01efc5c2634a7a87a6c7990533e8bea11e7a64a19870d061d314b

See more details on using hashes here.

Provenance

The following attestation bundles were made for antidrift_zeromcp-0.2.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.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for antidrift_zeromcp-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0ec74bf8387e40a1d7ac5ff075818b37bff13ac0c81c12c11008c90bf389b72e
MD5 2b00566236707a79007fdbc1022dff68
BLAKE2b-256 a6fc0f5f1d68d6f0f2084d850ca4baa86a763c05297062aa6c899330da9e0a6a

See more details on using hashes here.

Provenance

The following attestation bundles were made for antidrift_zeromcp-0.2.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