This is 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
- Python 3
- Clone the repo
git clone https://github.com/rcarmo/umcp- Try it out
echo '{"jsonrpc": "2.0", "method": "tools/call", "params": {"name": "get_movies"}, "id": 1}' | python ./movie_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 β
βββββββββββββββββ
This implementation includes two simple example servers that demonstrate how to use the MCP protocol, one for a movie booking system and another for a calculator.
Both are supplied in synchronous and asynchronous versions, showcasing how to implement tools and introspection.
In addition to tool discovery, the servers expose reusable prompt templates using a simple naming convention:
- Any method named
prompt_<name>is treated as a prompt definition. - The method docstring becomes the prompt description (first line as summary, rest as detail).
- Its signature is introspected and converted into a JSON Schema input definition (same mechanism as tools).
- Optional categories can be embedded in the docstring to help clients filter prompts.
You can declare categories in the docstring using any of the following forms (caseβinsensitive):
Category: review
Categories: code, quality
[category: explanation]
[categories: summarization, docs]
All collected category tokens are lowerβcased, deβduplicated, and returned as a list.
When the host calls prompts/get with arguments, the underlying prompt_<name> method is invoked. Its return value is normalized into a messages array as follows:
| Return Type | Interpretation |
|---|---|
str |
Wrapped as a single user message ({"role": "user", "content": {"type": "text", "text": ...}}) |
list of message dicts |
Used as-is if each item has role and content |
dict containing a messages list |
That list is used directly |
| Any other object | JSON-serialized into a single user message |
If the host omits arguments, only the prompt description (and categories) are returnedβno invocation occurs. This keeps lightweight discovery cheap.
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 key problems and actionable improvements concisely."
)
def prompt_summary(self, topic: str, bullets: int = 5):
"""Return a structured summarization dialogue.\n[categories: summary, documentation]"""
return [
{"role": "system", "content": {"type": "text", "text": "You are a precise technical summarizer."}},
{"role": "user", "content": {"type": "text", "text": f"Summarize '{topic}' in {bullets} bullet points."}},
]{"jsonrpc": "2.0", "id": 1, "method": "prompts/list"}Example (truncated) response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"prompts": [
{
"name": "code_review",
"description": "Generate a focused code review instruction.",
"inputSchema": {"type": "object", "properties": {"filename": {"type": "string"}, "issues": {"type": "integer"}}, "required": ["filename"]},
"categories": ["code", "review"]
}
]
}
}{
"jsonrpc": "2.0",
"id": 2,
"method": "prompts/get",
"params": {"name": "code_review", "arguments": {"filename": "main.py", "issues": 3}}
}Example response payload (messages shortened):
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"description": "Generate a focused code review instruction.",
"categories": ["code", "review"],
"messages": [
{"role": "user", "content": {"type": "text", "text": "Please review 'main.py'..."}}
]
}
}In the async server (AsyncMCPServer), prompt methods may themselves be async def. The dispatcher awaits them transparently; mixed sync/async prompt definitions are supported.
See:
tests/test_prompts.pyβ sync prompt behaviorstests/test_async_prompts.pyβ async + mixed return forms
These illustrate categories, description-only retrieval, and multi-message templates.
Prompts mirror tools for discoverability but return message scaffolds instead of βtool resultsβ. This enables clients (e.g., Copilot Chat) to:
- Offer pre-built structured prompt options
- Dynamically parameterize prompt templates via introspected schemas
- Reduce boilerplate in higher-level orchestrators
- Update VS Code settings.json
- Use with GitHub Copilot Chat
/mcp my-weather-server get weather for New York
- 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.
This project is licensed under the MIT License - see the LICENSE file for details.