python-patterns-mcp
by air237
README.md
# python-patterns-mcp
> A **Model Context Protocol (MCP)** server that exposes the 23 Gang of Four
> design patterns to AI coding agents — **generation**, **canonical examples**,
> **AST-based detection**, **validation**, and **anti-pattern refactoring**
> for **Python** codebases.
[](https://www.python.org/)
[](https://modelcontextprotocol.io)
[](./LICENSE)
This is the Python sibling of [java-patterns-mcp](https://github.com/air237/java-patterns-mcp).
Same 7-tool API, same intent — but every example, detector, validator, and
refactoring is rewritten around **Python idioms** (`__new__`, `dataclass`,
`abc.ABC`, generators, `weakref.WeakSet`, `functools.singledispatch`, …).
---
## Why a Python version
Generic LLMs can describe design patterns, but their generated Python code
often misses Pythonic alternatives — a class-based Strategy when a callable
parameter would do, a metaclass Singleton when a module would suffice, a
hand-rolled iterator when a generator function is one line shorter. And no
LLM can deterministically scan a real codebase to say *"this class is a
half-broken Singleton because `__init__` re-runs on every call"*.
This MCP server fills that gap with **deterministic, AST-backed tooling**.
## Pythonic decisions baked into the catalog
Every entry in `patterns.json` carries a `python_note` that the agent reads
through `list_patterns`. The notes tell the LLM **when *not* to reach for
the classic GoF recipe** — because Python has a better idiom for it.
| Category | Pattern | Classic GoF | Pythonic alternative the catalog suggests |
|---|---|---|---|
| Creational | **Abstract Factory** | abstract `Factory` + concrete factory classes | `abc.ABC` makes the contract explicit; concrete factories typically live as module-level singletons |
| Creational | **Builder** | dedicated `XBuilder` with telescoping methods | keyword arguments + `@dataclass`; Builder only justified for fluent chaining, staged construction, or cross-field validation |
| Creational | **Factory Method** | abstract `Creator.createX()` | a `classmethod` factory or a plain module-level function — duck typing means clients don't need the interface |
| Creational | **Prototype** | abstract `clone()` on every product | `copy.copy` / `copy.deepcopy` — the pattern is essentially built into the language |
| Creational | **Singleton** | private ctor + static getter + metaclass | a module-level object (modules are cached in `sys.modules`); `__new__` override if a class is really required |
| Structural | **Adapter** | wrapper class with a translated interface | often unnecessary — duck typing lets you call the original directly; a thin wrapper *function* usually suffices |
| Structural | **Bridge** | abstraction × implementation hierarchies | composition over inheritance — plain composition with a `Protocol` or `ABC` defining the implementor |
| Structural | **Composite** | abstract `Component` + Leaf + Composite | duck typing lets Leaf and Composite share an implicit interface; `abc.ABC` only when you want the contract explicit |
| Structural | **Decorator** | `Decorator` base wrapping an `Operation` | **NOT** the same as `@decorator` syntax (that's definition-time); object-Decorator is justified when you need to stack behaviour at runtime |
| Structural | **Facade** | `Facade` class hiding subsystem details | a module's `__init__.py` is a natural facade — only the curated names go into `__all__` |
| Structural | **Flyweight** | flyweight factory + intrinsic/extrinsic split | `sys.intern()` for strings, `functools.lru_cache` for functions, `weakref.WeakValueDictionary` for objects |
| Structural | **Proxy** | proxy class forwarding every method | `__getattr__` / `__getattribute__` make transparent proxies trivial; `weakref.proxy` is a built-in lifetime proxy |
| Behavioral | **Chain of Responsibility** | `Handler` base + `set_next()` | a list of callables and a small dispatch loop is usually enough |
| Behavioral | **Command** | `Command` interface + `execute()` | a callable + closure already captures "a request"; full Command shines when you need `undo()` + serialisability |
| Behavioral | **Interpreter** | one class per grammar rule | rarely needed in pure form — Python's own `ast` module is a fine reference when you do need it |
| Behavioral | **Iterator** | `Iterator` interface with `next()` | the iterator protocol (`__iter__` / `__next__`) is native; generator functions collapse it to a single `yield` |
| Behavioral | **Mediator** | central `Mediator` wired to every colleague | often combined with an EventBus / pub-sub; `asyncio.Queue` is a lightweight mediator for coroutines |
| Behavioral | **Memento** | `Memento` + `Caretaker` classes | `copy.deepcopy` + a stack; or `__getstate__` / `__setstate__` (the pickle protocol) for richer cases |
| Behavioral | **Observer** | `Subject` + `Observer` interfaces | `weakref.WeakSet` to avoid listener leaks; **snapshot** the subscriber list before iterating to survive concurrent unsubscribes |
| Behavioral | **State** | one class per state | a callable stored on the context — first-class functions are an alternative to a class hierarchy |
| Behavioral | **Strategy** | abstract `Strategy` + concrete classes | a callable parameter (`sorted(xs, key=fn)`); class-based Strategy only when the strategy carries state |
| Behavioral | **Template Method** | abstract base with `final` algorithm + hooks | `abc.ABC` lets you mark only the variable steps as `@abstractmethod`, freezing the algorithm in the concrete method |
| Behavioral | **Visitor** | `accept(visitor)` on every element | `functools.singledispatch` — dispatch on the runtime type of the first argument; `ast.NodeVisitor` is a built-in example |
The detectors and validators are tuned for this. For example, the Strategy
detector intentionally flags an abstract `*Strategy` class only if there are
**two or more concrete subclasses** — so the agent never gets nagged about
a one-variant "Strategy" that should just be a function.
## Tools
| Tool | What it does |
|---|---|
| `ping` | Health check — server name, version, registered tools |
| `list_patterns` | All 23 GoF patterns with intent / problem / Python note / refactoring.guru URL |
| `pattern_examples` | Canonical, runnable Pythonic example source(s) for a pattern |
| `generate_pattern` | Render a customised implementation (your `type_name`, your `module_name`) |
| `detect_pattern` | AST-based detection: inline source, file list, or recursive directory scan |
| `validate_pattern` | Pattern-specific code-quality check (ERROR / WARNING / INFO) |
| `refactor_to_pattern` | Idempotent AST rewrite — turn anti-pattern code into proper pattern |
### Coverage in this build
| Capability | Supported patterns |
|---|---|
| `list_patterns` | All **23** GoF patterns |
| `pattern_examples` | All **23** GoF patterns |
| `generate_pattern` | All **23** GoF patterns |
| `detect_pattern` | All **23** GoF patterns |
| `validate_pattern` | **18** patterns (Group A + Group B; Group C — Prototype, Flyweight, Interpreter, Iterator, Memento — is deliberately detection-only, mirroring Java `COVERAGE.md`) |
| `refactor_to_pattern` | **13** idempotent recipes across 11 patterns |
#### Per-pattern feature matrix
Legend: **A** = full 4-tool + refactor · **B** = 4-tool, no refactor · **C** = detection-only.
| Pattern | Category | Tier | Examples | Generator | Detector | Validator | Refactorings |
|---|---|---|:---:|:---:|:---:|:---:|:---|
| Singleton | Creational | A | ✅ | ✅ | ✅ | ✅ | `singleton-add-new-guard`, `singleton-add-init-once-guard`, `singleton-add-threading-lock`, `singleton-add-get-instance` |
| Factory Method | Creational | A | ✅ | ✅ | ✅ | ✅ | `factory-method-make-creator-abstract` |
| Builder | Creational | A | ✅ | ✅ | ✅ | ✅ | *(no reducible recipe)* |
| Adapter | Structural | A | ✅ | ✅ | ✅ | ✅ | `adapter-privatize-adaptee` |
| Decorator | Structural | A | ✅ | ✅ | ✅ | ✅ | `decorator-privatize-wrappee` |
| Composite | Structural | A | ✅ | ✅ | ✅ | ✅ | `composite-privatize-children` |
| Proxy | Structural | A | ✅ | ✅ | ✅ | ✅ | `proxy-privatize-subject` |
| Command | Behavioral | A | ✅ | ✅ | ✅ | ✅ | `command-mark-implementations-final` |
| Observer | Behavioral | A | ✅ | ✅ | ✅ | ✅ | `observer-snapshot-iteration` |
| State | Behavioral | A | ✅ | ✅ | ✅ | ✅ | `state-mark-implementations-final` |
| Strategy | Behavioral | A | ✅ | ✅ | ✅ | ✅ | *(no reducible recipe)* |
| Template Method | Behavioral | A | ✅ | ✅ | ✅ | ✅ | `template-method-mark-final` |
| Abstract Factory | Creational | B | ✅ | ✅ | ✅ | ✅ | — |
| Bridge | Structural | B | ✅ | ✅ | ✅ | ✅ | — |
| Facade | Structural | B | ✅ | ✅ | ✅ | ✅ | — |
| Chain of Responsibility | Behavioral | B | ✅ | ✅ | ✅ | ✅ | — |
| Mediator | Behavioral | B | ✅ | ✅ | ✅ | ✅ | — |
| Visitor | Behavioral | B | ✅ | ✅ | ✅ | ✅ | — |
| Prototype | Creational | C | ✅ | ✅ | ✅ | — | — |
| Flyweight | Structural | C | ✅ | ✅ | ✅ | — | — |
| Interpreter | Behavioral | C | ✅ | ✅ | ✅ | — | — |
| Iterator | Behavioral | C | ✅ | ✅ | ✅ | — | — |
| Memento | Behavioral | C | ✅ | ✅ | ✅ | — | — |
See [`COVERAGE.md`](./COVERAGE.md) for the same matrix plus the Java-parity delta.
## Tools — full reference with examples
Every example below shows the `tools/call` arguments and a (truncated)
`result.content[0].text` payload. The server always returns JSON-encoded
text, so the agent's call wrapper can `json.loads` it directly.
### 1. `ping` — health check
**Arguments:** none.
```json
→ {}
← {
"server": "python-patterns-mcp",
"version": "0.1.0",
"tools": ["ping", "list_patterns", "pattern_examples", "generate_pattern",
"detect_pattern", "validate_pattern", "refactor_to_pattern"]
}
```
### 2. `list_patterns` — catalog of all 23 GoF patterns
**Arguments:**
- `category` *(optional)* — `"Creational"`, `"Structural"`, `"Behavioral"`,
or any case variant. Omit to get all 23.
```json
→ { "category": "Creational" }
← {
"count": 5,
"patterns": [
{
"id": "SINGLETON", "display_name": "Singleton", "slug": "singleton",
"category": "Creational",
"intent": "Ensures a class has only one instance …",
"problem": "You need exactly one instance of a class …",
"python_note": "Python idiom: a module-level object is already a singleton …",
"aliases": ["Borg"],
"reference_url": "https://refactoring.guru/design-patterns/singleton"
},
…
]
}
```
### 3. `pattern_examples` — canonical Pythonic examples
**Arguments:**
- `pattern` *(required)* — enum name (`"SINGLETON"`), slug (`"singleton"`),
or display name (`"Singleton"`). Case-insensitive.
- `include_source` *(optional, default `true`)* — set to `false` for a
metadata-only response (lighter token cost when you only need filenames).
```json
→ { "pattern": "observer", "include_source": false }
← {
"pattern": {
"id": "OBSERVER", "display_name": "Observer", "slug": "observer",
"category": "Behavioral",
"reference_url": "https://refactoring.guru/design-patterns/observer",
"python_note": "Iterate over a snapshot of subscribers when emitting …"
},
"example_count": 1,
"files": [{ "file_name": "event_bus.py",
"relative_path": "examples/observer/event_bus.py",
"source": null }]
}
```
### 4. `generate_pattern` — render a customised implementation
**Arguments:**
- `pattern` *(required)* — one of `singleton`, `builder`, `strategy`, `observer`.
- `type_name` *(required)* — the main class name (e.g. `"Logger"`,
`"Pizza"`). Must be a valid Python identifier.
- `module_name` *(optional)* — overrides the snake_case derived from
`type_name`. Used in docstrings/comments only.
```json
→ { "pattern": "singleton", "type_name": "Config" }
← {
"pattern": "SINGLETON", "type_name": "Config", "module_name": "",
"file_count": 1,
"files": [{
"file_name": "config.py",
"source": "\"\"\"Thread-safe Singleton — generated by python-patterns-mcp.\n…\"\"\"\n\nimport threading\n\nclass Config:\n _instance: \"Config | None\" = None\n _lock: threading.Lock = threading.Lock()\n\n def __new__(cls, *args, **kwargs) -> \"Config\":\n if cls._instance is None:\n with cls._lock:\n …\n"
}]
}
```
Unsupported patterns return a clear error pointing at `pattern_examples`:
```json
→ { "pattern": "visitor", "type_name": "AstWalker" }
← ToolError: "Generation not yet implemented for VISITOR. Try pattern_examples instead. Supported: BUILDER, OBSERVER, SINGLETON, STRATEGY"
```
### 5. `detect_pattern` — AST-based scan
**Arguments (pass exactly one of):**
- `source` — inline Python source as a single string.
- `paths` — list of absolute / relative `.py` file paths on disk.
- `directory` — directory to scan recursively (cap: 1000 `.py` files).
```json
→ {
"source": "import threading\nclass Logger:\n _instance = None\n _lock = threading.Lock()\n def __new__(cls):\n if cls._instance is None:\n with cls._lock:\n if cls._instance is None:\n cls._instance = super().__new__(cls)\n return cls._instance\n @classmethod\n def get_instance(cls): return cls()\n"
}
← {
"file_count": 1, "hit_count": 1, "errors": [],
"supported_patterns": ["SINGLETON", "BUILDER", "STRATEGY", "OBSERVER"],
"hits": [{
"pattern": "SINGLETON", "display_name": "Singleton",
"location": "Logger", "file": "<inline>", "line": 2,
"confidence": 0.95,
"signals": [
"class attribute '_instance' initialised to None",
"__new__ returns cached instance",
"get_instance() factory method"
]
}]
}
```
Parse failures (per file) are reported in `errors` and never abort the batch.
### 6. `validate_pattern` — flag implementation pitfalls
**Arguments:**
- `source` *(required)* — Python source to inspect.
- `pattern` *(optional)* — narrow the check to one pattern; omit to run
every supported validator.
```json
→ {
"pattern": "singleton",
"source": "class Logger:\n _instance = None\n def __new__(cls):\n if cls._instance is None:\n cls._instance = super().__new__(cls)\n return cls._instance\n def __init__(self):\n self.log = []\n"
}
← {
"scope": "SINGLETON", "issue_count": 3,
"errors": [],
"warnings": [
{ "severity": "WARNING", "location": "Logger", "line": 1,
"message": "Singleton class defines both __new__ and __init__. __init__ runs on every instantiation …",
"suggestion": "Either guard __init__ with 'if self._initialized: return', or move initialisation into a private _initialize() method called once from __new__." },
{ "severity": "WARNING", "location": "Logger", "line": 3,
"message": "__new__ does not appear to use a threading.Lock. Two threads calling the class concurrently can each create a separate instance …",
"suggestion": "Add a class-level 'threading.Lock' and a double-checked lock around the 'if cls._instance is None' branch." }
],
"infos": [
{ "severity": "INFO", "location": "Logger", "line": 1,
"message": "No get_instance() classmethod found. Calling the class directly works …",
"suggestion": "Add 'def get_instance(cls) -> Self: return cls()' as a @classmethod for readability." }
]
}
```
### 7. `refactor_to_pattern` — idempotent AST rewrite
**Arguments:**
- `source` *(required)* — Python source to rewrite.
- `refactoring` *(required)* — one of
`singleton-add-new-guard`, `singleton-add-init-once-guard`.
```json
→ {
"refactoring": "singleton-add-init-once-guard",
"source": "class Logger:\n _instance = None\n def __new__(cls):\n if cls._instance is None:\n cls._instance = super().__new__(cls)\n return cls._instance\n def __init__(self):\n self.log = []\n"
}
← {
"refactoring": "singleton-add-init-once-guard",
"changed": true,
"log": ["Added once-only __init__ guard to class 'Logger' at line 7"],
"source": "class Logger:\n _instance = None\n\n def __new__(cls):\n if cls._instance is None:\n cls._instance = super().__new__(cls)\n return cls._instance\n\n def __init__(self):\n if getattr(self, '_initialized', False):\n return\n self._initialized = True\n self.log = []\n",
"supported_refactorings": ["singleton-add-init-once-guard", "singleton-add-new-guard"]
}
```
Re-running the same refactoring on already-correct code is a no-op
(`changed: false`, empty `log`).
## End-to-end agent workflow
A typical conversation: *"clean up this Logger so it's actually a singleton"*.
```text
1. detect_pattern { source: <user's code> }
→ SINGLETON @ Logger, confidence 0.6 — only 1 signal fired
2. validate_pattern { source: …, pattern: "singleton" }
→ 2 WARNINGs (__init__ re-runs, no Lock) + 1 INFO (no get_instance)
3. refactor_to_pattern { source: …, refactoring: "singleton-add-init-once-guard" }
→ rewritten source with `_initialized` guard at the top of __init__
4. validate_pattern { source: <rewritten>, pattern: "singleton" }
→ only the Lock warning + get_instance INFO remain
→ agent decides to add a threading.Lock by hand, then declares done
```
## Requirements
- **Python 3.11+** (uses `Self`, `slots=True` dataclasses, `match`-friendly enums)
- **`mcp` SDK** ≥ 1.2.0 (provides `FastMCP`)
- **`jinja2`** ≥ 3.1.0 (for `generate_pattern`)
## Install
Pick whichever Python package manager you already have. All three give you
the same working server.
### Option A — `uv` (fastest, recommended)
```bash
git clone https://github.com/<you>/python-patterns-mcp.git
cd python-patterns-mcp
uv venv
uv pip install -e ".[dev]"
```
The interpreter for OpenCode wiring is then `.venv/bin/python`.
### Option B — `pipx` (one-shot, isolated, no venv juggling)
If you just want to *use* the server (not develop on it):
```bash
pipx install git+https://github.com/<you>/python-patterns-mcp.git
```
`pipx` exposes the `python-patterns-mcp` console script on your `$PATH`,
which you can plug straight into OpenCode without any path gymnastics:
```jsonc
{ "mcp": { "python-patterns": { "type": "local",
"command": ["python-patterns-mcp"] } } }
```
### Option C — plain `pip` + `venv`
```bash
git clone https://github.com/<you>/python-patterns-mcp.git
cd python-patterns-mcp
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
```
> **Behind a corporate `pip.conf`?** Some SAP/internal mirrors fail name
> resolution off-VPN and `pip` hangs on retries. Bypass with:
> ```bash
> PIP_CONFIG_FILE=/dev/null pip install -e ".[dev]"
> ```
## Run
```bash
python -m python_patterns_mcp
```
The server speaks the standard MCP stdio transport — stdin for JSON-RPC
requests, stdout for JSON-RPC responses, **stderr** for all log lines (so
stdout stays clean). It blocks until stdin closes.
A convenience console script is also installed: `python-patterns-mcp`.
## Smoke test
```bash
python tests/smoke_stdio.py
```
This script boots the server as a subprocess, exchanges 7 JSON-RPC frames,
and prints the parsed responses for `initialize`, `tools/list`, `ping`,
`list_patterns`, `detect_pattern`, `generate_pattern`, and `pattern_examples`.
Expected tail:
```
Received 7 response(s) on stdout
[initialize] serverInfo={'name': 'python-patterns-mcp', 'version': '0.1.0'}
[tools/list] 7 tools: ping, list_patterns, pattern_examples, generate_pattern,
detect_pattern, validate_pattern, refactor_to_pattern
[ping] {'server': 'python-patterns-mcp', 'version': '0.1.0', ...}
[list_patterns] 5 entries
[detect_pattern] 1 hit(s); errors=[]
• SINGLETON @ Logger (conf=0.95, signals=3)
[generate_pattern] produced 1 file: config.py
[pattern_examples] pattern=ITERATOR, files=1: name_roster.py
```
## Unit tests
```bash
pytest -v
# 117 passed in 0.6s
```
The test suite covers every tool end-to-end. A particularly satisfying test
is `test_refactored_singleton_runs`: it takes a plain class, applies two
refactorings in sequence, **executes** the result, and asserts the rewritten
class genuinely behaves like a singleton.
## Wire into OpenCode
Add to `~/.config/opencode/opencode.json`:
```jsonc
{
"mcp": {
"python-patterns": {
"type": "local",
"command": [
"/Users/<you>/git/com/python-patterns-mcp/.venv/bin/python",
"-m",
"python_patterns_mcp"
]
}
}
}
```
OpenCode then sees all 7 tools under the `python-patterns` namespace.
## Project layout
```
python-patterns-mcp/
├── pyproject.toml
├── README.md
├── LICENSE
├── resources/
│ ├── catalog/patterns.json ← refactoring.guru-style metadata
│ ├── examples/<slug>/*.py ← canonical Pythonic examples
│ └── templates/<slug>/*.py.jinja ← code-generation templates
├── src/python_patterns_mcp/
│ ├── __init__.py
│ ├── __main__.py ← `python -m python_patterns_mcp`
│ ├── server.py ← FastMCP bootstrap
│ ├── catalog/ ← Pattern enum + registry + examples
│ ├── tools/ ← 7 MCP tool handlers
│ ├── generate/ ← Jinja2 generator
│ ├── detect/ ← AST-based detectors
│ ├── validate/ ← pattern-quality validators
│ └── refactor/ ← AST rewriters
└── tests/
├── test_catalog.py
├── test_examples.py
├── test_generator.py
├── test_detect.py
├── test_validate.py
├── test_refactor.py
├── test_server.py
└── smoke_stdio.py ← end-to-end stdio smoke test
```
## Roadmap
The architecture lets you grow any of the five capability tables just by
**adding one file per pattern** — no changes to the existing engines.
The initial full-parity milestone is done — every GoF pattern has an
example, a generator, and a detector, and Group A + B additionally have
validators. What's still open is either **out of scope** or **quality
improvement** rather than "coverage".
### Out-of-scope
* **`validate_pattern` for Group C** (Prototype, Flyweight, Interpreter,
Iterator, Memento) — matches Java's `COVERAGE.md` decision. The pitfalls
in these five patterns are too diffuse to reduce to a small set of
actionable rules; adding validators here tends to produce noise.
* **`refactor_to_pattern` for Group B** (Abstract Factory, Bridge, Facade,
Visitor, Chain of Responsibility, Mediator) — these patterns' typical
fixes require design-level judgement, not a one-line AST edit.
### Nice-to-haves
* **Detector confidence tuning** — every pattern already emits a
0.0..1.0 score and a `signals[]` breakdown. Fine-tuning the base +
per-signal weights against a wider corpus of real Python code would
reduce false positives further.
* **More validator rules** — e.g. Observer listener leak (missing
`weakref`), Iterator single-shot detection, Decorator delegation
completeness, Factory Method concrete-override coverage.
* **Custom refactorings** — e.g. `builder-make-fields-final` (convert a
Builder's product into a frozen `@dataclass(slots=True)`).
### Project-level work
* GitHub Actions CI (mirror the Java repo's `mvn verify` setup)
* PyPI publish workflow
* `mcp dev` / MCP Inspector wiring for interactive exploration
* More tests around the `directory` mode of `detect_pattern`
(large recursive scans, parse-error reporting)
PRs welcome. The simplest way to contribute is to open an issue with a
real-world Python snippet where a detector misfires — the fix is usually
a single-line adjustment to the pattern's signal weights.
## License
[MIT](./LICENSE) © 2026 contributors.
Pattern examples are adapted from
[refactoring.guru](https://refactoring.guru/design-patterns/python) and the
original *Design Patterns: Elements of Reusable Object-Oriented Software*
(Gamma, Helm, Johnson, Vlissides). All adapted code is original
re-implementation; no third-party source is reproduced verbatim.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues