Skip to main content
Glama
README.md
# ropey

Safe, project-wide Python refactoring for coding agents. ropey is an
[MCP](https://modelcontextprotocol.io) server that exposes the
[rope](https://github.com/python-rope/rope) refactoring library as tools a
coding agent (Claude in Claude Code, OpenCode, or any MCP client) can call.

## Why

Coding agents read and navigate Python well. An LSP like Astral's ty
answers "where is this defined?" and "who references it?" precisely. What
agents lack is a safe way to *change* Python structurally. Renaming a symbol
used across thirty files by hand-editing text is slow and unreliable: the
agent can't be sure it found every reference, can't prove a textual match is
the same binding, and routinely leaves half-renamed code.

ropey closes that gap with one division of labour:

> ty finds and reads; ropey changes.

The agent points at code using the exact `line`/`character` coordinates its
LSP already returned, and ropey performs the behaviour-preserving
transformation across the whole project: rename, move, extract, inline,
change signature, organise imports, and so on. One sibling tool, `rewrite`,
takes a pattern→goal transformation for structural changes none of the
refactorings express, addressed by a code template rather than a
coordinate (see the safety contract).

## The safety contract

- **Behaviour is preserved, with one exception.** Every refactoring
  alters the structure of your code without changing what it does, and
  rope proves each transformation safe before ropey writes it. `rewrite`
  is the exception: it makes no behaviour-preservation claim. The agent
  asserts that pattern and goal are equivalent, and the tool guarantees
  only that it rewrites exactly the matches it reports. In exchange it
  surfaces *every* Match Site (file + range, flagged `matched` or
  `unsure`) for the agent to audit, leaves unprovable sites un-rewritten
  unless explicitly opted in, and refuses any rewrite that would produce
  unparsable Python. Both preview and apply mode enforce that refusal.
- **Dry Run by default.** Every tool takes an `apply` flag. With
  `apply=false` (the default) the full consequence is computed and reported
  but nothing is written. `apply=true` performs the same change for real.
  Both report identical detail.
- **The Blast Radius.** Every result enumerates every affected file with
  what happened to it: `modified`, `created`, `moved` (with its old path),
  or `deleted`. The list is never truncated and never carries file
  contents. After a live run, `git diff` shows the exact text.
- **Uncertain Occurrences.** Python is dynamically typed, so sometimes rope
  cannot prove that `obj.save()` refers to the method being renamed. ropey
  applies only the certain occurrences and reports every uncertain one as a
  flagged location for the agent to adjudicate. Nothing is silently included
  or silently dropped.
- **Freshness is self-established.** Before every refactoring the server
  re-checks the source on disk, so edits from any writer are reflected,
  whether they came from the agent, a human editor, `git checkout`, or a
  formatter. Correctness never depends on the host announcing its edits.
- **git is the undo.** ropey writes no cache artifacts into your repo
  (no `.ropeproject/`), never edits gitignored files (git couldn't revert
  them), and recommends a clean working tree before applying so `git diff`
  / `git checkout` are always a complete reversal mechanism.
- **Failures are structured.** A refactoring that cannot proceed returns a
  machine-readable reason ("the selection crosses a scope boundary", "the
  file `broken.py` cannot be parsed") rather than a stack trace.

## Install

### Claude Code (plugin marketplace)

```
/plugin marketplace add andrewesweet/ropey
/plugin install ropey@ropey
```

The plugin bundles the MCP server config; tools appear after a restart.
Requires [uv](https://docs.astral.sh/uv/) on your PATH.

### OpenCode

Add to `opencode.json`:

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "ropey": {
      "type": "local",
      "command": [
        "uvx", "--from", "git+https://github.com/andrewesweet/ropey", "ropey"
      ],
      "enabled": true
    }
  }
}
```

### Any other MCP client

ropey is a standard stdio MCP server. Generic config:

```json
{
  "mcpServers": {
    "ropey": {
      "command": "uvx",
      "args": ["--from", "git+https://github.com/andrewesweet/ropey", "ropey"]
    }
  }
}
```

Or run it directly: `uvx --from git+https://github.com/andrewesweet/ropey ropey`

## The catalogue

| Tool | What it does |
| ---- | ------------ |
| `rename` | Rename a symbol everywhere, optionally in docstrings/comments and across a class hierarchy |
| `move` | Move a global, a method, or a whole module; imports updated project-wide |
| `module_to_package` | Convert a module file into a package |
| `extract_method` / `extract_variable` | Extract a selection into a helper or a named value |
| `inline` | Inline a method, variable, or parameter (kind auto-detected) |
| `change_signature` | Add / remove / reorder parameters with every call site updated |
| `organize_imports` | Sort, dedupe, expand star-imports, relative→absolute |
| `introduce_parameter` | Turn a selected expression into a new parameter that defaults to it, with call sites updated |
| `encapsulate_field` | Wrap a class attribute behind getter/setter; reads and writes rewritten project-wide |
| `introduce_factory` | Add a factory (static method or module function) for a class and route instantiations through it |
| `method_object` | Convert a method into a method object (a class whose `__call__` holds the body) to decompose a complex method |
| `local_to_field` | Promote a method-local variable to an instance field (`self.<name>`) |
| `use_function` | Replace code that duplicates a function's body with calls to it, project-wide |
| `rewrite` | Pattern→goal rewrite of every matching site (`${obj}.get_attribute(${key})` → `${obj}[${key}]`), with per-wildcard match constraints, certainty-flagged Match Sites, and a syntax guard |

Targets are addressed with LSP coordinates (0-based line/character, UTF-16
units), the same coordinates an LSP returns; byte offsets never appear.
Point refactorings accept an optional `expected_symbol` so a stale position
fails loudly instead of refactoring the wrong code. The exception is
`rewrite`, which is addressed by a *pattern* (Python source with
`${wildcard}` placeholders) instead of a coordinate, and reports its Match
Sites back as LSP ranges. ty finds and reads; ropey changes.

## Development

```bash
uv sync
uv run pytest
```

Domain documentation lives in [`CONTEXT.md`](CONTEXT.md), the decision
records in [`docs/adr/`](docs/adr/), and the PRD in
[`docs/prd/`](docs/prd/). Operability notes and measured latency envelopes:
[`docs/operability.md`](docs/operability.md).

TDQS

A4.6/5.0

Scored across 15 tools

Disambiguation5/5

Each tool targets a distinct refactoring operation (rename, move, extract, inline, signature changes, etc.). Even potentially overlapping tools like rewrite and extract_method are clearly differentiated by their descriptions and explicit guidance on when to use the dedicated tool.

Naming Consistency4/5

All names use snake_case and are generally descriptive, but there's a mix of grammatical patterns: single verbs (rename, move), verb-noun (extract_method, change_signature), and noun-based phrases (method_object, local_to_field). This is mostly consistent with a refactoring domain, though a more uniform verb-first pattern would be slightly clearer.

Tool Count5/5

15 tools is within the desirable range for a specialized refactoring server. Each tool covers a well-known Python refactoring, and none feel redundant or unnecessary. The set is neither too thin nor bloated.

Completeness4/5

The server covers a broad spectrum of common refactorings, including structural changes (move, module_to_package), local transformations (extract_variable, local_to_field), and signature/class operations. Some advanced refactorings like 'pull up' or 'extract class' are absent, but the core workflows are well represented, and the rewrite tool provides a fallback for custom transformations.

Maintenance

ActivityInactive
ResponsivenessResponsive