Command Palette

Search for a command to run...

UnylyUnyly
Весь каталог

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

GitHubEmbed

Описание

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.

Python 3.11+ MCP License: MIT

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, default true) — set to false for 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 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.
→ { "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 .py file paths on disk.
  • directory — directory to scan recursively (cap: 1000 .py files).
→ {
  "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 of singleton-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=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)

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 and pip hangs 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__ returning self while __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-checked with 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 — change for x in self._listeners: to for x in list(self._listeners): inside emit.

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 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.

from github.com/air237/python-patterns-mcp

Установка Python Patterns

У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.

▸ github.com/air237/python-patterns-mcp

FAQ

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

Compare Python Patterns with

Не уверен что выбрать?

Найди свой стек за 60 секунд

Автор?

Embed-бейдж для README

Похожее

Все в категории design