Metadata-Version: 2.4
Name: sublime-mcp
Version: 1.2.3
Summary: MCP server for Sublime Text 4 — lets Claude Code read and control a running ST instance
Author-email: Donald Chitester <donaldchitester@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/dpc00/sublime-mcp
Project-URL: Repository, https://github.com/dpc00/sublime-mcp
Keywords: mcp,sublime-text,claude,ai,editor
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Text Editors
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: mcp
Requires-Dist: httpx

# sublime-mcp

<!-- mcp-name: io.github.dpc00/sublime-mcp -->

The most complete Sublime Text / AI integration available. sublime-mcp lets
Claude Code (or any MCP client) do anything in ST that a human can do from the
keyboard — read files, navigate, edit with full undo history, search across the
project, run builds, send commands to a Terminus terminal, evaluate arbitrary
Python in ST's main thread, and more. If you use ST as your primary editor,
this closes the gap between "AI that edits files" and "AI that works in your
editor."

63 tools covering reading, navigation, editing, searching, build, Terminus
integration, settings, layout, menus, console log, and live Python scripting.

## Architecture

```
Claude Code (MCP client)
        │  stdio / MCP protocol
        ▼
  mcp_server.py          ← Python process you run outside ST
        │  HTTP  127.0.0.1:9500
        ▼
  sublime_mcp.py         ← ST plugin, HTTP server on ST's main thread
        │  sublime API
        ▼
  Sublime Text 4
```

| File | Role |
|------|------|
| `sublime_mcp.py` | ST plugin — runs an HTTP server inside Sublime Text |
| `mcp_server.py` | MCP server — wraps the HTTP API for MCP clients |

## Installation

### 1. Install the ST plugin

Copy `sublime_mcp.py` to your Sublime Text `Packages/User/` folder:

```
%APPDATA%\Sublime Text\Packages\User\sublime_mcp.py
```

ST loads it automatically on start (or via `Tools › Developer › New Plugin…`
then save over it). You should see:

```
sublime-mcp: listening on 127.0.0.1:9500
```

in the ST console (`View › Show Console`).

### 2. Install the MCP server

```
pip install sublime-mcp
```

### 3. Register with Claude Code

Add to `~/.claude/mcp.json` (or `~/.claude/settings.json`):

```json
{
  "mcpServers": {
    "sublime-mcp": {
      "command": "sublime-mcp"
    }
  }
}
```

Then restart Claude Code. Tools will appear with the `mcp__sublime-mcp__` prefix.

## Windows + WSL setup

If you run Sublime Text on **both** Windows and WSL, you can give Claude Code
in each environment its own MCP entry pointing at the local ST instance, plus
an optional cross-side entry for the other one.

**Requirements:** WSL2 with [mirrored networking](https://learn.microsoft.com/en-us/windows/wsl/networking#mirrored-mode-networking)
(Windows 11 default). This makes `127.0.0.1` on the WSL side reach Windows
ports, and vice versa.

### How it works

| ST instance | Port | Reached from |
|---|---|---|
| Windows ST | `9500` | Windows `127.0.0.1:9500` or WSL `127.0.0.1:9500` (mirrored) |
| WSL ST | `9501` | WSL `127.0.0.1:9501` |

Each ST instance runs its own copy of `sublime_mcp.py` on its own port. The
MCP server process always runs on the same side as Claude Code — it just points
at the right port via `SUBLIME_MCP_BASE`.

### Step 1 — Install the plugin in both ST instances

**Windows ST** — copy to `%APPDATA%\Sublime Text\Packages\User\sublime_mcp.py`.
Leave `_PORT = 9500` (the default).

**WSL ST** — copy to `~/.config/sublime-text/Packages/User/sublime_mcp.py`
and change the port:

```python
_PORT = 9501   # line 22 of sublime_mcp.py
```

### Step 2 — Install the MCP server on both sides

**Windows:**
```
pip install sublime-mcp
```

**WSL:**
```
pip3 install sublime-mcp
```

### Step 3 — Windows `~/.claude/mcp.json`

```json
{
  "mcpServers": {
    "sublime-mcp": {
      "command": "sublime-mcp"
    },
    "sublime-mcp-wsl": {
      "command": "wsl",
      "args": ["sublime-mcp"],
      "env": { "SUBLIME_MCP_BASE": "http://127.0.0.1:9501" }
    }
  }
}
```

- `sublime-mcp` runs as a Windows process → auto-detects port `9500` → Windows ST
- `sublime-mcp-wsl` runs inside WSL via the `wsl` command → `127.0.0.1:9501` is WSL's loopback → WSL ST

### Step 4 — WSL `~/.claude/mcp.json`

```json
{
  "mcpServers": {
    "sublime-mcp": {
      "command": "sublime-mcp",
      "env": { "SUBLIME_MCP_BASE": "http://127.0.0.1:9501" }
    },
    "sublime-mcp-win": {
      "command": "sublime-mcp",
      "env": { "SUBLIME_MCP_BASE": "http://127.0.0.1:9500" }
    }
  }
}
```

- `sublime-mcp` runs in WSL → `127.0.0.1:9501` → WSL ST
- `sublime-mcp-win` runs in WSL → `127.0.0.1:9500` reaches Windows ST via mirrored networking

### Tool prefixes

| Claude Code session | Tool prefix for WSL ST | Tool prefix for Windows ST |
|---|---|---|
| Windows | `mcp__sublime-mcp-wsl__` | `mcp__sublime-mcp__` |
| WSL | `mcp__sublime-mcp__` | `mcp__sublime-mcp-win__` |

## Tab and Sheet Indexing

**IMPORTANT:** Users refer to tabs by 1-based numbering (tab 1, tab 2, etc.), but 
`get_sheets()` returns 0-based indexes. Always convert user tab references before using:
- User **tab 1** = index 0
- User **tab 2** = index 1
- User **tab 3** = index 2
- etc.

When closing or targeting a specific tab, always verify the index by calling `get_sheets()` 
first, and close by path (preferred) or by focusing then closing the active file. 
**Never change focus without user awareness.**

## Tools

### Read / Introspect

| Tool | Description |
|------|-------------|
| `get_active_file` | Path, full content, cursor line/col, dirty flag, and syntax name |
| `get_selection` | Current selection(s): text and begin/end line+col for each |
| `get_cursor_context` | `lines` lines above and below cursor, with 1-based line numbers prepended |
| `get_open_files` | All files open in the current window (path, name, is_dirty) |
| `get_sheets` | All sheets (tabs) in the window by index — includes images and untitled buffers. Returns index, type, path, name, is_dirty |
| `get_sheet_content` | Content of any tab by sheet index (from `get_sheets`). Works for text, untitled, and Terminus tabs; returns path only for image tabs |
| `get_project_folders` | Project root folder paths |
| `get_file_content` | Full content of any already-open file by path |
| `get_view_content` | Full content of any open tab by name (partial match). Works for Terminus tabs and nameless views |
| `get_view_size` | Total character count of any open tab. Use to compute offsets before `get_view_chars` |
| `get_view_chars` | Text at character offsets begin..end (0-based, end exclusive). Clamps to buffer bounds |
| `get_view_phantoms` | Phantom HTML and extracted plain text from a named view; filters by phantom key |
| `get_output_panel` | Text content of a named output panel. Omit name for the active panel; `name='exec'` for build output |
| `get_active_panel` | Active panel id and, if it is an output panel, its content |
| `get_symbols` | All symbols (functions, classes, etc.) in the active file with line numbers |
| `lookup_symbol` | Find where a symbol is defined across all open files |
| `get_project_data` | Raw `.sublime-project` JSON for the current project |
| `get_variables` | ST build variables: `$file`, `$project_path`, `$platform`, etc. |
| `get_command_palette` | Command Palette entries from installed `*.sublime-commands` resources; filterable by package, command, or caption |
| `get_commands` | Runnable command ids from loaded command classes, optionally merged with palette metadata |
| `get_menu_items` | Menu items from `*.sublime-menu` resources; filterable by menu filename, caption, or command |
| `get_syntaxes` | All syntax definitions available in ST (name + path) |
| `get_scope_at_cursor` | Full syntax scope string at the cursor position |
| `get_word_at_cursor` | Word under the cursor and its line/col |
| `get_bookmarks` | All bookmarked positions in the active file |
| `get_line_count` | Total number of lines in the active file |
| `get_encoding` | Character encoding of the active file |
| `get_setting` | A ST setting by key. `scope='view'` (default) or `'window'` |
| `get_layout` | Current window layout (groups, cells) and which files are in each group |

### Navigate

| Tool | Description |
|------|-------------|
| `open_file` | Open a file, optionally jumping to a specific line and column |
| `goto_line` | Move cursor to line (and optional column) in the active file |
| `show_panel` | Bring an output panel to the front. Default `name='exec'` for the build panel |
| `focus_group` | Move focus to a pane group by 0-based index |

### Edit

| Tool | Description |
|------|-------------|
| `str_replace_based_edit_tool` | ST-native editor: `str_replace` (unique match), `insert` (after line N), `create` (new file), `view` (numbered content). Full undo, gutter diff, 30s highlight. Auto-opens file if needed |
| `replace_selection` | Replace the current selection(s) with text |
| `replace_lines` | Replace lines begin..end (inclusive, 1-based) in the active file |
| `insert_snippet` | Insert at the cursor using ST snippet syntax (`$1` for tab stops, etc.) |
| `duplicate_line` | Duplicate the current line(s) |
| `toggle_comment` | Toggle line comment, or block comment if `block=True` |
| `sort_lines` | Sort selected lines, or all lines if nothing is selected |
| `select_lines` | Select lines begin..end (1-based, inclusive) |
| `fold_lines` | Fold (collapse) lines begin..end in the active file |
| `undo` | Undo the last edit |
| `redo` | Redo the last undone edit |
| `run_command` | Run any ST command with optional args. `scope='window'` (default) or `'view'` |

### Search

| Tool | Description |
|------|-------------|
| `find_in_file` | Find all occurrences of a pattern in the active file. Returns `{line, col, text}` list |
| `find_in_files` | Search across project folders (or a supplied list). Skips `.git`, `__pycache__`, `node_modules`, `.venv`. Returns `{path, line, match}` list, capped at `max_results` (default 200) |

### File / Project

| Tool | Description |
|------|-------------|
| `save_file` | Save the active file |
| `save_all` | Save all open files |
| `close_file` | Close a file by path, or the active file if path is omitted |
| `revert_file` | Revert the active file to its last saved state |
| `add_folder` | Add a folder to the current project (no-op if already present) |
| `remove_folder` | Remove a folder from the current project by path |

### Syntax / Encoding

| Tool | Description |
|------|-------------|
| `set_syntax` | Set the syntax of the active file by name (case-insensitive partial match) |
| `set_encoding` | Set the character encoding of the active file (e.g. `'UTF-8'`, `'Western (Windows 1252)'`) |

### Settings / Window

| Tool | Description |
|------|-------------|
| `set_setting` | Set a ST setting by key. `scope='view'` (default) or `'window'` |
| `toggle_sidebar` | Show or hide the sidebar |
| `set_layout` | Set the window pane layout. Accepts a ST layout dict with `cols`, `rows`, `cells` |
| `set_status` | Write a message to ST's status bar |

### Build

| Tool | Description |
|------|-------------|
| `run_build` | Trigger the current build system, or pass `cmd`/`shell_cmd` + `working_dir` for a custom command |

### Terminus Integration

[Terminus](https://github.com/randy3k/Terminus) is a popular ST terminal package.
`send_to_view` is Terminus-aware: when targeting a Terminus tab it uses
`terminus_send_string` to type text into the terminal session rather than
inserting into a buffer.

| Tool | Description |
|------|-------------|
| `send_to_view` | Send a string to any open tab by name. For Terminus tabs, types the text as if the user typed it. Include a trailing `\n` to execute a command |

### Scripting

| Tool | Description |
|------|-------------|
| `eval_python` | Execute arbitrary Python in ST's main thread. Locals available: `sublime`, `window`, `view`, `print`. Returns captured stdout in `output` |
| `get_console_log` | Recent ST console output (plugin log messages and stdout). `tail=N` limits to last N entries; `tail=0` returns all |

## Configuration

### Port

The plugin listens on `9500` by default. The MCP server auto-detects the port:
- Windows → `9500`
- WSL / Linux → `9501`

Override with the `SUBLIME_MCP_BASE` environment variable:

```json
{
  "mcpServers": {
    "sublime-mcp": {
      "command": "sublime-mcp",
      "env": { "SUBLIME_MCP_BASE": "http://127.0.0.1:9500" }
    }
  }
}
```

To change the plugin's port, edit `_PORT` in `sublime_mcp.py`.

### Timeout

The MCP server waits up to 10 seconds for each HTTP response. Edit `TIMEOUT` in
`mcp_server.py` if you need longer (e.g. for slow `eval_python` calls).

## Security note

The HTTP server binds to `127.0.0.1` only and accepts any request without
authentication. Do not expose port 9500 to a network interface.

## Requirements

- Sublime Text 4
- Python 3.10+ (for the MCP server process)
- `pip install mcp httpx`
- Terminus package (optional, required only for `send_to_view` on terminal tabs)

## Getting Claude to use the tools well

Add a section like this to your project's `CLAUDE.md` (or `~/.claude/CLAUDE.md`
for global use):

```markdown
## Sublime Text MCP tools

sublime-mcp is connected. Prefer it over standard file tools when working in ST:

- Read files with `get_active_file` or `get_file_content` rather than Read tool
- Edit with `str_replace_based_edit_tool` — edits appear live with gutter diff and undo
- Use `find_in_files` for project-wide search
- Use `send_to_view` to run commands in a Terminus terminal tab
- Use `eval_python` for one-off ST scripting (no plugin file needed)
- Check `get_console_log` when a plugin isn't behaving as expected

Tab indexing: `get_sheets()` returns 0-based indexes; users refer to tabs
1-based. Always call `get_sheets()` first when targeting a specific tab.
```

## Known limitations / Roadmap

- **No multi-window support** — tools target the most recently focused ST window only
- **No image editing** — `get_sheet_content` returns the path for image tabs, not pixel data
- **Terminus dependency is optional** — `send_to_view` degrades gracefully if Terminus isn't installed, but terminal interaction requires it
- **No ST3 support** — the plugin uses ST4 APIs throughout

Contributions welcome in any of these areas. See below.

## Testing

The test suite requires Sublime Text to be running with `sublime_mcp.py` loaded.

```
cd C:\Users\donal\projects\sublime-mcp
pip install httpx pytest
pytest tests/test_http_api.py -v
```

111 tests covering all 60 HTTP API endpoints. Expected result: 109 passed, 2 skipped
(the skips are environment-dependent: one requires an open saved file, one requires
a Terminus tab). Zero failures on a clean ST session.

## Contributing

The project has two independent pieces — the ST plugin (`sublime_mcp.py`) and
the MCP server (`mcp_server.py`) — which makes it easy to contribute to either
without touching the other.

**Adding a tool:**
1. Add an HTTP handler in `sublime_mcp.py` (runs on ST's main thread via `_on_main`)
2. Add the corresponding `@mcp.tool()` function in `mcp_server.py`
3. Add a row to the Tools table in `README.md`

**Good first issues:**
- Multi-window support (`sublime.windows()` instead of `sublime.active_window()`)
- `get_diagnostics` — expose LSP error/warning annotations
- `set_bookmark` / `clear_bookmarks` — write counterparts to `get_bookmarks`
- Any gap identified in the [JetBrains MCP feature set](https://github.com/JetBrains/mcp-jetbrains)

Open an issue or PR on [GitHub](https://github.com/dpc00/sublime-mcp).
