🚀 mcpy-cli: Transform Python functions into production-ready MCP services

mcpy-cli
is a powerful toolkit designed to simplify creating, running, and deploying Model Context Protocol (MCP) services. It transforms ordinary Python functions into fully-featured MCP tools with automatic schema generation, endpoint creation, and interactive documentation.
✨ Key Features
- 📦 Automatic Function Discovery: Scans Python files and detects functions without additional markup required
- 🚀 Flexible Deployment Options:
run
command with hot reload, ideal for local development
package
command with start scripts for production deployment
- 🔄 An MCP of two modes:
- Composed Mode: All tools under a single endpoint with automatic namespacing
- Routed Mode: Microservice-style with directory-based routing
- 🌐 Complete JSON-RPC Implementation: Full compliance with MCP protocol specification
- 🎨 Type-Safe by Design: Automatic validation using Python type hints and docstrings
- 🔌 Multiple Transport Options: Modern streamable HTTP (default) and legacy SSE support
🔥 Quick Start
Installation
pip install mcpy-cli
pip install uv
uv pip install mcpy-cli
Building Your First MCP Service
- Create a Python file with functions:
def add(a: float, b: float) -> float:
"""Add two numbers and return the result"""
return a + b
def multiply(a: float, b: float) -> float:
"""Multiply two numbers and return the result"""
return a * b
- Create another file for different functionality:
def concatenate(text1: str, text2: str) -> str:
"""Join two text strings together"""
return text1 + text2
def word_count(text: str) -> int:
"""Count words in a text string"""
return len(text.split())
mcpy-cli run --source-path ./ --port 8080 --reload True
uvx mcpy-cli run --source-path ./ --port 8080 --reload True
- Open
http://localhost:8080/mcp-server/mcp
in your browser using tools like MCP Inspector
- In Composed mode (default):
tool_math_tools_add
, tool_text_tools_word_count
- In Routed mode: Navigate to each module's endpoint
Production Packaging
- Package your service for deployment:
mcpy-cli package --source-path ./my_project --package-name math-text-tools
unzip math-text-tools.zip
cd math-text-tools/project
chmod +x start.sh
./start.sh
- Deployment Structure:
The package contains:
- Your source code in its original structure
- A generated
start.sh
script with all necessary parameters
- A
requirements.txt
file with all dependencies
- README files with usage instructions
🥯️ Two MCP service structures
📋 Composed Mode (Default)
Technical Benefits:
- ✅ Single ASGI Application: All tools are handled by one Starlette app
- ✅ Shared Session State: Tools can share state within a session
- ✅ Reduced Resource Overhead: Only one FastMCP instance runs at the server level
- ✅ Automatic Naming Convention: Tools are prefixed with file name (e.g.,
tool_math_add
)
- ✅ Unified Authentication: Apply auth to all tools at once
Best for:
- Applications requiring unified API access
- Tools that work together cooperatively
- Simplified client integration
Usage:
mcpy-cli run --source-path ./my_tools --mode composed
🔀 Routed Mode
Technical Benefits:
- ✅ True Microservices: Each module runs as an independent MCP server
- ✅ Namespace Isolation: Tools retain original names without prefixing
- ✅ Selective Scaling: Deploy and scale modules independently
- ✅ Independent State: No shared state between different modules
- ✅ Clean URL Hierarchy: Directory structure is directly reflected in URLs
Best for:
- Large projects or enterprise applications
- Modular deployment and management needs
- Team collaboration with different people maintaining different modules
- Independent scaling of specific functionalities
Usage:
mcpy-cli run --source-path ./my_tools --mode routed
🏆 Comprehensive Mode Comparison
Architecture | Monolithic | Microservices |
URL Structure | /mcp-server/mcp (single endpoint) | /math_tools/mcp , /text_tools/mcp (multiple) |
Tool Naming | Prefixed: tool_file_function | Original: function |
Session State | Shared across all tools | Isolated per module |
Resource Usage | Lower (single FastMCP instance) | Higher (multiple instances) |
Use Case | Cohesive, related functionality | Distinct, separate domains |
🔄 When to Choose Each Mode
Choose Composed Mode when:
- You want a simple, unified API
- Your tools are logically related
- You need to minimize resource usage
- You prefer simplified deployment
- You have a single team managing all tools
Choose Routed Mode when:
- You need strong module isolation
- Different teams manage different modules
- You want fine-grained scaling control
- Your tools serve distinct domains
- You need independent versioning or deployment
🌐 Deployment Options
Local Development
mcpy-cli run --source-path ./my_project --reload True
mcpy-cli run --source-path ./my_project --legacy-sse
mcpy-cli run --source-path ./my_project --host 0.0.0.0 --port 9000
mcpy-cli run --source-path ./my_project --mcp-name CustomTools --server-root /api
Transport Configuration
Default: Streamable HTTP (Recommended)
- Single endpoint, more stable
- Better compatibility with cloud environments
- No path issues with proxies or load balancers
Legacy SSE Transport (Deprecated)
- Dual-endpoint architecture (SSE + messages)
- May encounter issues in cloud environments (Knative/Istio)
- Only use for backward compatibility when required
mcpy-cli run --source-path ./my_tools
mcpy-cli run --source-path ./my_tools --legacy-sse
Containerized Deployment
Create a Dockerfile
for your packaged service:
FROM python:3.10-slim
WORKDIR /app
# Copy packaged service contents
COPY my-service/ .
# Install dependencies
RUN pip install --no-cache-dir -r project/requirements.txt
# Default command runs the service
CMD ["/bin/bash", "project/start.sh"]
# Expose service port
EXPOSE 8080
Production Deployment Strategies
-
ASGI Server with Uvicorn/Gunicorn:
- Your packaged
start.sh
already uses Uvicorn
- For production, consider using Gunicorn as a process manager:
gunicorn -k uvicorn.workers.UvicornWorker -w 4 main:app
-
Kubernetes Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: mcp-service
spec:
replicas: 3
-
Serverless Functions (AWS Lambda, Google Cloud Functions):
- Use Mangum for AWS Lambda adaptation:
from mangum import Mangum
handler = Mangum(app)
📚 Client Integration
Python Client Examples
Direct HTTP Client (Standard Library)
import json
import urllib.request
def call_mcp_tool(tool_name, params, endpoint="http://localhost:8080/mcp-server/mcp"):
payload = {
"jsonrpc": "2.0",
"method": tool_name,
"params": params,
"id": 1
}
data = json.dumps(payload).encode('utf-8')
req = urllib.request.Request(
endpoint,
data=data,
headers={'Content-Type': 'application/json'}
)
with urllib.request.urlopen(req) as response:
return json.loads(response.read().decode('utf-8'))
result = call_mcp_tool("tool_math_tools_add", {"a": 10, "b": 5})
print(f"Result: {result['result']}")
FastMCP Native Client (Async)
import asyncio
from fastmcp import FastMCP
async def main():
client = FastMCP("http://localhost:8080/mcp-server/mcp")
tools = await client.list_tools()
print(f"Available tools: {', '.join(t.id for t in tools)}")
result = await client.call_tool("tool_math_tools_multiply", {"a": 4, "b": 7})
print(f"4 × 7 = {result}")
result = await client.call_tool("tool_text_tools_concatenate",
{"text1": "Hello ", "text2": "World!"})
print(result)
asyncio.run(main())
JavaScript/TypeScript Client
async function callMcpTool(toolName: string, params: Record<string, any>) {
const response = await fetch('http://localhost:8080/mcp-server/mcp', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
jsonrpc: '2.0',
method: toolName,
params: params,
id: 1,
}),
});
return await response.json();
}
const result = await callMcpTool('tool_math_tools_add', { a: 3, b: 7 });
console.log(`The sum is: ${result.result}`);
🧀 Advanced Configuration
1. Service Persistence
mcpy-cli
supports service persistence and state recovery through event storage (EventStore). When enabled, the service stores JSON-RPC messages, allowing execution to resume from specific event points after service interruption or restart.
- Implementation: Default uses
SQLiteEventStore
, saving event data in a local SQLite database file.
- Enabling: Start the service with the
--enable-event-store
flag.
- Database Path: Specify with
--event-store-path
parameter. Default is ./mcp_event_store.db
.
mcpy-cli run --source-path ./my_tools --enable-event-store --event-store-path ./my_service_events.db
This feature is particularly useful for MCP services that need to run for extended periods or maintain session state.
2. Caching
To improve performance and reduce redundant computation, the tool provides session-level tool call caching (SessionToolCallCache
).
- Mechanism: This in-memory cache stores tool call results within specific user sessions. When the same tool is called again with identical parameters in the same session, results can be returned directly from the cache without re-executing the tool function.
- Use Case: This cache is primarily activated and effective in "stateful JSON response mode".
- Lifecycle: Cache content is bound to the user session and is cleared when the session ends or is cleared.
This mechanism helps optimize response speed for tools that may be frequently called within a session.
⚙️ Configuration
Command Line Options
Common Options (for all commands)
--source-path | Path to Python files/directory | Current directory |
--log-level | Logging level (debug, info, warning, error) | info |
--functions | Comma-separated specific functions to expose | All discovered functions |
--mcp-name | MCP server name | MCPModelService |
--server-root | Root path for MCP service group | /mcp-server |
--mcp-base | Base path for MCP protocol endpoints | /mcp |
--mode | Architecture mode (composed/routed) | composed |
--cors-enabled | Enable CORS middleware | True |
--cors-allow-origins | Allowed CORS origins (comma-separated) | * (all origins) |
Run Command Options
--host | Network interface to bind | 127.0.0.1 |
--port | Service port | 8080 |
--reload | Enable auto-reload for development | False |
--workers | Number of worker processes | 1 |
--enable-event-store | Enable SQLite event store for persistence | False |
--event-store-path | Path for event store database | ./mcp_event_store.db |
--stateless-http | Enable stateless HTTP mode | False |
--json-response | Use JSON response format instead of SSE | False |
Package Command Options
--package-name | Base name for output package | Required (no default) |
--package-host | Host to configure in start script | 0.0.0.0 |
--package-port | Port to configure in start script | 8080 |
--package-reload | Enable auto-reload in packaged service | False |
--package-workers | Number of workers in packaged service | 1 |
--mw-service | ModelWhale service mode | True |
📖 Documentation & Support
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.