A lightweight, zero-overhead implementation of Model Context Protocol (MCP) in pure Python inspired by the original bash implementation by Muthukumaran Navaneethakrishnan.
Why? I found the idea of using the simplest possible implementation of MCP in a shell script fascinating, but I wanted to see how it would look in Python with true introspection capabilities.
- β Full JSON-RPC 2.0 protocol over stdio
- β Complete MCP protocol implementation
- β Dynamic tool discovery via function naming convention
- β Complete introspection of function signatures
- β Easy to extend with custom tools
- β Prompt templates for reusable, structured interactions
- β Both synchronous and asynchronous implementations
- β Zero third-party dependencies
- Python 3.7+
- Clone the repository
git clone https://github.com/rcarmo/umcp
cd umcp- Verify installation
python movie_server.py --helpNo additional packages required - MicroMCP uses only the Python standard library!
echo '{"jsonrpc": "2.0", "method": "tools/call", "params": {"name": "get_movies"}, "id": 1}' | python ./movie_server.pyecho '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}' | python ./movie_server.pyecho '{"jsonrpc": "2.0", "method": "tools/call", "params": {"name": "add", "arguments": {"a": 5, "b": 3}}, "id": 1}' | python ./calculator_server.pyβββββββββββββββ βββββββββββββββββ
β MCP Host β β MCP Server β
β (AI System) βββββββββΊ β (myserver.py) β
βββββββββββββββ stdio βββββββββββββββββ
β
βββββββββββ΄ββββββββββββββββββββββββββββββββ
βΌ βΌ βΌ
ββββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββββββ
β Protocol Layer β β Business Logic β β Prompt Templates β
β (umcp.py) β β(tool_* methods)β β (prompt_* methods) β
ββββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββββββ
β β
βΌ βΌ
βββββββββββββββββ βββββββββββββββββ
β Introspection β β External β
βββββββββββββββββ β Services/APIs β
βββββββββββββββββ
Create a file my_server.py:
#!/usr/bin/env python3
from umcp import MCPServer
from typing import Dict, Any, Optional
class MyServer(MCPServer):
"""A simple example MCP server."""
def tool_greet(self, name: str = "World") -> str:
"""Greet someone by name.
Args:
name: The name to greet
Returns:
A friendly greeting message
"""
return f"Hello, {name}!"
def tool_add_numbers(self, a: float, b: float) -> float:
"""Add two numbers together.
Args:
a: First number
b: Second number
Returns:
The sum of the two numbers
"""
return a + b
if __name__ == "__main__":
server = MyServer()
server.run()# Make it executable
chmod +x my_server.py
# Test the greet tool
echo '{"jsonrpc": "2.0", "method": "tools/call", "params": {"name": "greet", "arguments": {"name": "Alice"}}, "id": 1}' | ./my_server.py
# Test the add tool
echo '{"jsonrpc": "2.0", "method": "tools/call", "params": {"name": "add_numbers", "arguments": {"a": 10, "b": 5}}, "id": 2}' | ./my_server.pyCreate async_server.py:
#!/usr/bin/env python3
import asyncio
from aioumcp import AsyncMCPServer
class AsyncMyServer(AsyncMCPServer):
"""An async example MCP server."""
async def tool_fetch_data(self, url: str) -> Dict[str, Any]:
"""Simulate fetching data from a URL.
Args:
url: The URL to fetch from
Returns:
Mock data response
"""
await asyncio.sleep(0.1) # Simulate network delay
return {"url": url, "status": "success", "data": "mock response"}
if __name__ == "__main__":
server = AsyncMyServer()
server.run()This implementation includes two example servers that demonstrate how to use the MCP protocol:
- Demonstrates CRUD operations
- Shows parameter validation
- Includes prompt templates for movie-related tasks
- Simple mathematical operations
- Error handling for edge cases
- Type-safe parameter handling
Both are supplied in synchronous and asynchronous versions, showcasing how to implement tools and introspection.
# Synchronous versions
python movie_server.py
python calculator_server.py
# Asynchronous versions
python async_movie_server.py
python async_calculator_server.pyMicroMCP supports reusable prompt templates using a simple naming convention. See PROMPTS.md for detailed documentation.
- Any method named
prompt_<name>is treated as a prompt definition - The method docstring becomes the prompt description
- Function signature is introspected for JSON Schema input definition
- Optional categories can be embedded in the docstring
class MyServer(MCPServer):
def prompt_code_review(self, filename: str, issues: int = 0) -> str:
"""Generate a focused code review instruction.\nCategories: code, review"""
return f"Please review '{filename}'. Assume ~{issues} pre-identified issues."# List available prompts
echo '{"jsonrpc": "2.0", "method": "prompts/list", "id": 1}' | python ./movie_server.py
# Get a specific prompt
echo '{"jsonrpc": "2.0", "method": "prompts/get", "params": {"name": "code_review", "arguments": {"filename": "main.py"}}, "id": 2}' | python ./movie_server.pyBase class for synchronous MCP servers.
Key Methods:
discover_tools()- Automatically finds alltool_*methodsdiscover_prompts()- Automatically finds allprompt_*methodshandle_tools_call()- Dispatches tool executionhandle_prompt_get()- Handles prompt template retrieval
Base class for asynchronous MCP servers.
Key Methods:
discover_tools()- Automatically finds alltool_*methodsdiscover_prompts()- Automatically finds allprompt_*methodshandle_tools_call()- Dispatches tool execution (async)handle_prompt_get()- Handles prompt template retrieval (async)
def tool_<name>(self, param1: type1, param2: type2 = default) -> return_type:
"""Tool description (first line becomes summary).
Args:
param1: Description of parameter 1
param2: Description of parameter 2
Returns:
Description of return value
"""
# Implementationdef prompt_<name>(self, param1: type1, param2: type2 = default) -> return_type:
"""Prompt description.\nCategories: category1, category2"""
# Implementation returning str, list, or dict# Run all tests
python -m pytest tests/
# Run specific test files
python -m pytest tests/test_introspection.py
python -m pytest tests/test_prompts.py
python -m pytest tests/test_async_prompts.py
# Run with verbose output
python -m pytest tests/ -vThe test suite covers:
- Introspection: Tool and prompt discovery
- Protocol Compliance: JSON-RPC 2.0 implementation
- Synchronous Operations: Tool execution and prompt handling
- Asynchronous Operations: Async tool execution and prompt handling
- Error Handling: Proper error responses and logging
- Performance: Async vs sync comparisons
import subprocess
import json
def test_my_server():
# Test tool discovery
result = subprocess.run([
'echo', '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}'
], capture_output=True, text=True)
response = json.loads(result.stdout)
assert 'result' in response
assert 'tools' in response['result']# Clone the repository
git clone https://github.com/rcarmo/umcp
cd umcp
# Create a virtual environment (optional but recommended)
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Run tests to verify everything works
python -m pytest tests/This project follows these conventions:
- Explicit imports only
- Functional programming style
- Short, single-responsibility functions
- Type hints for all parameters/returns
- Double quotes for strings
- Triple-double quote docstrings
snake_casemethod naming- f-strings only when needed
- Logging over print statements
- Fork the repository
- Create a feature branch:
git checkout -b feature-name - Make your changes following the code style
- Add tests for new functionality
- Ensure all tests pass:
python -m pytest tests/ - Submit a pull request
umcp/
βββ README.md # This file
βββ LICENSE # MIT License
βββ .github/
β βββ copilot-instructions.md # AI assistant guidelines
βββ umcp.py # Synchronous MCP server base class
βββ aioumcp.py # Asynchronous MCP server base class
βββ movie_server.py # Example movie booking server
βββ async_movie_server.py # Async version of movie server
βββ calculator_server.py # Example calculator server
βββ async_calculator_server.py # Async version of calculator
βββ tests/ # Test suite
β βββ test_introspection.py
β βββ test_prompts.py
β βββ test_async_prompts.py
β βββ ...
βββ PROMPTS.md # Detailed prompt documentation
- Update VS Code settings.json
- Use with GitHub Copilot Chat
/mcp my-weather-server get weather for New York
Add to your Claude Desktop configuration:
{
"mcpServers": {
"my-server": {
"command": "python",
"args": ["/path/to/your/server.py"],
"env": {}
}
}
}- No concurrency/parallel processing in synchronous version
- No streaming responses
- Not designed for high throughput
For AI assistants and local tool execution, these aren't blocking issues.
Q: Server doesn't respond to JSON-RPC requests A: Check that your JSON is valid and that the server is running properly. Try testing with a simple tools/list request first.
Q: Tools not showing up in tools/list
A: Ensure your tool methods are named tool_* and have proper type hints. Check the server logs for any introspection errors.
Q: Async server seems slow
A: The async examples use asyncio.sleep() to simulate I/O operations. In real applications, remove these delays.
Q: Permission denied on server script
A: Make the script executable: chmod +x your_server.py
Enable debug logging by setting the log level:
if __name__ == "__main__":
server = MyServer()
server.log_level = "DEBUG"
server.run()- Check the test files for working examples
- Review the prompt documentation for template guidance
- Open an issue on GitHub for bugs or feature requests
This project is licensed under the MIT License - see the LICENSE file for details.
- Inspired by the original
bashMCP implementation by Muthukumaran Navaneethakrishnan - Built on the Model Context Protocol specification
- Thanks to all contributors and the MCP community