Python Patterns
БесплатноНе проверенExposes the 23 Gang of Four design patterns to AI coding agents for generation, canonical examples, AST-based detection, validation, and anti-pattern refactorin
Описание
Exposes the 23 Gang of Four design patterns to AI coding agents for generation, canonical examples, AST-based detection, validation, and anti-pattern refactoring in Python codebases.
README
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.
This is the Python sibling of 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 |
8 patterns: Singleton, Builder, Factory Method, Strategy, Observer, Decorator, Adapter, Iterator |
generate_pattern |
4 patterns: Singleton, Builder, Strategy, Observer |
detect_pattern |
4 patterns: Singleton, Builder, Strategy, Observer |
validate_pattern |
3 patterns: Singleton, Builder, Strategy |
refactor_to_pattern |
2 refactorings: singleton-add-new-guard, singleton-add-init-once-guard |
Adding a new pattern is a matter of dropping a file in
resources/examples/<slug>/ and (for the AST tools) writing a small
detector / validator / refactoring class. See the Roadmap section below
for what's outstanding.
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.
→ {}
← {
"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.
→ { "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, defaulttrue) — set tofalsefor a metadata-only response (lighter token cost when you only need filenames).
→ { "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 ofsingleton,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 fromtype_name. Used in docstrings/comments only.
→ { "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:
→ { "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.pyfile paths on disk.directory— directory to scan recursively (cap: 1000.pyfiles).
→ {
"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.
→ {
"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 ofsingleton-add-new-guard,singleton-add-init-once-guard.
→ {
"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".
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=Truedataclasses,match-friendly enums) mcpSDK ≥ 1.2.0 (providesFastMCP)jinja2≥ 3.1.0 (forgenerate_pattern)
Install
Pick whichever Python package manager you already have. All three give you the same working server.
Option A — uv (fastest, recommended)
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):
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:
{ "mcp": { "python-patterns": { "type": "local",
"command": ["python-patterns-mcp"] } } }
Option C — plain pip + venv
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 andpiphangs on retries. Bypass with:PIP_CONFIG_FILE=/dev/null pip install -e ".[dev]"
Run
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
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
pytest -v
# 36 passed in 0.5s
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:
{
"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 (pattern_examples,
generate_pattern, detect_pattern, validate_pattern, refactor_to_pattern)
just by adding one file per pattern — no changes to the existing engines.
Per capability, this is what's still open:
pattern_examples — 15 patterns to go
Add a Pythonic example file under resources/examples/<slug>/ and the
loader picks it up automatically. Examples-loader test (tests/test_examples.py)
auto-asserts each example is valid Python.
| Priority | Pattern | Notes for the example |
|---|---|---|
| P1 | Composite | dataclass Leaf + Composite, duck-typed size() |
| P1 | Command | callable+closure variant and class-based with undo() |
| P1 | Template Method | abc.ABC with @abstractmethod for the variable step |
| P1 | Chain of Responsibility | list-of-callables variant and class-based with set_next() |
| P2 | Abstract Factory | ABC factory + two concrete factories |
| P2 | Proxy | __getattr__ transparent proxy + caching example |
| P2 | State | callable-on-context variant and class-based |
| P2 | Mediator | tiny chat-room mediator on top of asyncio.Queue |
| P2 | Visitor | functools.singledispatch + a tiny AST walker |
| P3 | Bridge | Shape × Renderer via composition + Protocol |
| P3 | Facade | __init__.py-style facade with curated __all__ |
| P3 | Flyweight | WeakValueDictionary-backed factory |
| P3 | Prototype | copy.deepcopy recipe + custom __copy__ hook |
| P3 | Memento | pickle-protocol-based snapshot/restore |
| P3 | Interpreter | tiny boolean-expression evaluator |
generate_pattern — 4 templates to go
Drop a resources/templates/<slug>/__type__.py.jinja and update the
_SUPPORTED set in generate/generator.py. The Jinja context exposes
type_name, module_name, and a pre-computed snake_name.
- Factory Method, Decorator, Adapter, Command — the four patterns where a generator pays off (the rest are either too small to template usefully, or too situation-specific).
detect_pattern — 8 detectors to go
Subclass PatternDetector, set self.pattern, walk the AST, return a
list of DetectedPattern. Register in detect/engine.py.
- Factory Method, Decorator, Adapter, Composite, Command, Iterator, Template Method, State.
validate_pattern — patterns waiting for a validator
Same recipe (PatternValidator subclass, register in validate/engine.py).
High-value targets where Python-specific mistakes are common:
- Observer — flag listeners stored without
weakref(leak risk), detect iteration over the live list instead of a snapshot. - Iterator — flag
__iter__returningselfwhile__next__is also destructive (single-shot iterator that masquerades as iterable). - Decorator — flag wrappers that forget to delegate one or more methods of the wrappee's interface.
- Factory Method — flag concrete subclasses that don't override the factory method.
refactor_to_pattern — more idempotent rewrites
Subclass Refactoring, set refactoring_id, return a RefactoringResult.
singleton-add-threading-lock— wrap the__new__body in a double-checkedwith cls._lock:block.singleton-add-get-instance— add the readability@classmethod.builder-make-fields-final— convert a Builder's product into a frozen@dataclass(slots=True).observer-snapshot-iteration— changefor x in self._listeners:tofor x in list(self._listeners):insideemit.
Project-level work
- GitHub Actions CI (mirror the Java repo's
mvn verifysetup) - PyPI publish workflow
mcp dev/ MCP Inspector wiring for interactive exploration- More tests around the
directorymode ofdetect_pattern(large recursive scans, parse-error reporting)
PRs welcome on any line above. The simplest first contribution is one
more pattern_examples entry — it's a single file, the test will
auto-cover it.
License
MIT © 2026 contributors.
Pattern examples are adapted from refactoring.guru 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.
Установка Python Patterns
У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.
▸ github.com/air237/python-patterns-mcpFAQ
Python Patterns MCP бесплатный?
Да, Python Patterns MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Python Patterns?
Нет, Python Patterns работает без API-ключей и переменных окружения.
Python Patterns — hosted или self-hosted?
Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.
Как установить Python Patterns в Claude Desktop, Claude Code или Cursor?
Открой Python Patterns на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
LibreOffice Tools
Enables AI agents to read, write, and edit Office documents via LibreOffice with token-efficient design. Supports multiple formats including DOCX, XLSX, PPTX, a
автор: passerbyflutterdannote/figma-use
Full Figma control: create shapes, text, components, set styles, auto-layout, variables, export. 80+ tools.
автор: dannoteLogo.dev
Search and retrieve company logos by brand or domain. Customize size, format, and theme to match your design needs. Accelerate design, prototyping, and content
автор: NOVA-3951PIX4Dmatic
Enables GUI automation for controlling PIX4Dmatic on Windows through MCP. Supports launching, focusing, capturing screenshots, sending hotkeys, clicking UI elem
автор: jangjo123Compare Python Patterns with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории design
