python-patterns-mcp
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
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.
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.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.