Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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.

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

→ {}
← {
  "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
# 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:

{
  "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 © 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.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages