sublime-mcp 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sublime_mcp-1.0.0/PKG-INFO +230 -0
- sublime_mcp-1.0.0/README.md +212 -0
- sublime_mcp-1.0.0/mcp_server.py +495 -0
- sublime_mcp-1.0.0/pyproject.toml +33 -0
- sublime_mcp-1.0.0/setup.cfg +4 -0
- sublime_mcp-1.0.0/sublime_mcp.egg-info/PKG-INFO +230 -0
- sublime_mcp-1.0.0/sublime_mcp.egg-info/SOURCES.txt +9 -0
- sublime_mcp-1.0.0/sublime_mcp.egg-info/dependency_links.txt +1 -0
- sublime_mcp-1.0.0/sublime_mcp.egg-info/entry_points.txt +2 -0
- sublime_mcp-1.0.0/sublime_mcp.egg-info/requires.txt +2 -0
- sublime_mcp-1.0.0/sublime_mcp.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sublime-mcp
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: MCP server for Sublime Text 4 — lets Claude Code read and control a running ST instance
|
|
5
|
+
Author-email: Donald Chitester <donaldchitester@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/dpc00/sublime-mcp
|
|
8
|
+
Project-URL: Repository, https://github.com/dpc00/sublime-mcp
|
|
9
|
+
Keywords: mcp,sublime-text,claude,ai,editor
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Text Editors
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: mcp
|
|
17
|
+
Requires-Dist: httpx
|
|
18
|
+
|
|
19
|
+
# sublime-mcp
|
|
20
|
+
|
|
21
|
+
<!-- mcp-name: io.github.dpc00/sublime-mcp -->
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
MCP server for Sublime Text 4. Lets Claude Code (or any MCP client) read and
|
|
25
|
+
control a running ST instance via a local HTTP bridge.
|
|
26
|
+
|
|
27
|
+
58 tools covering reading, navigation, editing, searching, build, Terminus
|
|
28
|
+
integration, settings, layout, and live Python scripting.
|
|
29
|
+
|
|
30
|
+
## Architecture
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
Claude Code (MCP client)
|
|
34
|
+
│ stdio / MCP protocol
|
|
35
|
+
▼
|
|
36
|
+
mcp_server.py ← Python process you run outside ST
|
|
37
|
+
│ HTTP 127.0.0.1:9500
|
|
38
|
+
▼
|
|
39
|
+
sublime_mcp.py ← ST plugin, HTTP server on ST's main thread
|
|
40
|
+
│ sublime API
|
|
41
|
+
▼
|
|
42
|
+
Sublime Text 4
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
| File | Role |
|
|
46
|
+
|------|------|
|
|
47
|
+
| `sublime_mcp.py` | ST plugin — runs an HTTP server inside Sublime Text |
|
|
48
|
+
| `mcp_server.py` | MCP server — wraps the HTTP API for MCP clients |
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
### 1. Install the ST plugin
|
|
53
|
+
|
|
54
|
+
Copy `sublime_mcp.py` to your Sublime Text `Packages/User/` folder:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
%APPDATA%\Sublime Text\Packages\User\sublime_mcp.py
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
ST loads it automatically on start (or via `Tools › Developer › New Plugin…`
|
|
61
|
+
then save over it). You should see:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
sublime-mcp: listening on 127.0.0.1:9500
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
in the ST console (`View › Show Console`).
|
|
68
|
+
|
|
69
|
+
### 2. Install the MCP server
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
pip install sublime-mcp
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 3. Register with Claude Code
|
|
76
|
+
|
|
77
|
+
Add to `~/.claude/settings.json`:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"mcpServers": {
|
|
82
|
+
"sublime": {
|
|
83
|
+
"command": "sublime-mcp"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Then restart Claude Code. Tools will appear with the `mcp__sublime__` prefix.
|
|
90
|
+
|
|
91
|
+
Then restart Claude Code. Tools will appear with the `mcp__sublime__` prefix.
|
|
92
|
+
|
|
93
|
+
## Tools
|
|
94
|
+
|
|
95
|
+
### Read / Introspect
|
|
96
|
+
|
|
97
|
+
| Tool | Description |
|
|
98
|
+
|------|-------------|
|
|
99
|
+
| `get_active_file` | Path, full content, cursor line/col, dirty flag, and syntax name |
|
|
100
|
+
| `get_selection` | Current selection(s): text and begin/end line+col for each |
|
|
101
|
+
| `get_cursor_context` | `lines` lines above and below cursor, with 1-based line numbers prepended |
|
|
102
|
+
| `get_open_files` | All files open in the current window (path, name, is_dirty) |
|
|
103
|
+
| `get_project_folders` | Project root folder paths |
|
|
104
|
+
| `get_file_content` | Full content of any already-open file by path |
|
|
105
|
+
| `get_view_content` | Full content of any open tab by name (partial match). Works for Terminus tabs and nameless views |
|
|
106
|
+
| `get_view_size` | Total character count of any open tab. Use to compute offsets before `get_view_chars` |
|
|
107
|
+
| `get_view_chars` | Text at character offsets begin..end (0-based, end exclusive). Clamps to buffer bounds |
|
|
108
|
+
| `get_view_phantoms` | Phantom HTML and extracted plain text from a named view; filters by phantom key |
|
|
109
|
+
| `get_output_panel` | Text content of a named output panel. Omit name for the active panel; `name='exec'` for build output |
|
|
110
|
+
| `get_active_panel` | Active panel id and, if it is an output panel, its content |
|
|
111
|
+
| `get_symbols` | All symbols (functions, classes, etc.) in the active file with line numbers |
|
|
112
|
+
| `lookup_symbol` | Find where a symbol is defined across all open files |
|
|
113
|
+
| `get_project_data` | Raw `.sublime-project` JSON for the current project |
|
|
114
|
+
| `get_variables` | ST build variables: `$file`, `$project_path`, `$platform`, etc. |
|
|
115
|
+
| `get_command_palette` | Command Palette entries from installed `*.sublime-commands` resources; filterable by package, command, or caption |
|
|
116
|
+
| `get_commands` | Runnable command ids from loaded command classes, optionally merged with palette metadata |
|
|
117
|
+
| `get_menu_items` | Menu items from `*.sublime-menu` resources; filterable by menu filename, caption, or command |
|
|
118
|
+
| `get_syntaxes` | All syntax definitions available in ST (name + path) |
|
|
119
|
+
| `get_scope_at_cursor` | Full syntax scope string at the cursor position |
|
|
120
|
+
| `get_word_at_cursor` | Word under the cursor and its line/col |
|
|
121
|
+
| `get_bookmarks` | All bookmarked positions in the active file |
|
|
122
|
+
| `get_line_count` | Total number of lines in the active file |
|
|
123
|
+
| `get_encoding` | Character encoding of the active file |
|
|
124
|
+
| `get_setting` | A ST setting by key. `scope='view'` (default) or `'window'` |
|
|
125
|
+
| `get_layout` | Current window layout (groups, cells) and which files are in each group |
|
|
126
|
+
|
|
127
|
+
### Navigate
|
|
128
|
+
|
|
129
|
+
| Tool | Description |
|
|
130
|
+
|------|-------------|
|
|
131
|
+
| `open_file` | Open a file, optionally jumping to a specific line and column |
|
|
132
|
+
| `goto_line` | Move cursor to line (and optional column) in the active file |
|
|
133
|
+
| `show_panel` | Bring an output panel to the front. Default `name='exec'` for the build panel |
|
|
134
|
+
| `focus_group` | Move focus to a pane group by 0-based index |
|
|
135
|
+
|
|
136
|
+
### Edit
|
|
137
|
+
|
|
138
|
+
| Tool | Description |
|
|
139
|
+
|------|-------------|
|
|
140
|
+
| `replace_selection` | Replace the current selection(s) with text |
|
|
141
|
+
| `replace_lines` | Replace lines begin..end (inclusive, 1-based) in the active file |
|
|
142
|
+
| `insert_snippet` | Insert at the cursor using ST snippet syntax (`$1` for tab stops, etc.) |
|
|
143
|
+
| `duplicate_line` | Duplicate the current line(s) |
|
|
144
|
+
| `toggle_comment` | Toggle line comment, or block comment if `block=True` |
|
|
145
|
+
| `sort_lines` | Sort selected lines, or all lines if nothing is selected |
|
|
146
|
+
| `select_lines` | Select lines begin..end (1-based, inclusive) |
|
|
147
|
+
| `fold_lines` | Fold (collapse) lines begin..end in the active file |
|
|
148
|
+
| `undo` | Undo the last edit |
|
|
149
|
+
| `redo` | Redo the last undone edit |
|
|
150
|
+
| `run_command` | Run any ST command with optional args. `scope='window'` (default) or `'view'` |
|
|
151
|
+
|
|
152
|
+
### Search
|
|
153
|
+
|
|
154
|
+
| Tool | Description |
|
|
155
|
+
|------|-------------|
|
|
156
|
+
| `find_in_file` | Find all occurrences of a pattern in the active file. Returns `{line, col, text}` list |
|
|
157
|
+
| `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) |
|
|
158
|
+
|
|
159
|
+
### File / Project
|
|
160
|
+
|
|
161
|
+
| Tool | Description |
|
|
162
|
+
|------|-------------|
|
|
163
|
+
| `save_file` | Save the active file |
|
|
164
|
+
| `save_all` | Save all open files |
|
|
165
|
+
| `close_file` | Close a file by path, or the active file if path is omitted |
|
|
166
|
+
| `revert_file` | Revert the active file to its last saved state |
|
|
167
|
+
| `add_folder` | Add a folder to the current project (no-op if already present) |
|
|
168
|
+
| `remove_folder` | Remove a folder from the current project by path |
|
|
169
|
+
|
|
170
|
+
### Syntax / Encoding
|
|
171
|
+
|
|
172
|
+
| Tool | Description |
|
|
173
|
+
|------|-------------|
|
|
174
|
+
| `set_syntax` | Set the syntax of the active file by name (case-insensitive partial match) |
|
|
175
|
+
| `set_encoding` | Set the character encoding of the active file (e.g. `'UTF-8'`, `'Western (Windows 1252)'`) |
|
|
176
|
+
|
|
177
|
+
### Settings / Window
|
|
178
|
+
|
|
179
|
+
| Tool | Description |
|
|
180
|
+
|------|-------------|
|
|
181
|
+
| `set_setting` | Set a ST setting by key. `scope='view'` (default) or `'window'` |
|
|
182
|
+
| `toggle_sidebar` | Show or hide the sidebar |
|
|
183
|
+
| `set_layout` | Set the window pane layout. Accepts a ST layout dict with `cols`, `rows`, `cells` |
|
|
184
|
+
| `set_status` | Write a message to ST's status bar |
|
|
185
|
+
|
|
186
|
+
### Build
|
|
187
|
+
|
|
188
|
+
| Tool | Description |
|
|
189
|
+
|------|-------------|
|
|
190
|
+
| `run_build` | Trigger the current build system, or pass `cmd`/`shell_cmd` + `working_dir` for a custom command |
|
|
191
|
+
|
|
192
|
+
### Terminus Integration
|
|
193
|
+
|
|
194
|
+
[Terminus](https://github.com/randy3k/Terminus) is a popular ST terminal package.
|
|
195
|
+
`send_to_view` is Terminus-aware: when targeting a Terminus tab it uses
|
|
196
|
+
`terminus_send_string` to type text into the terminal session rather than
|
|
197
|
+
inserting into a buffer.
|
|
198
|
+
|
|
199
|
+
| Tool | Description |
|
|
200
|
+
|------|-------------|
|
|
201
|
+
| `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 |
|
|
202
|
+
|
|
203
|
+
### Scripting
|
|
204
|
+
|
|
205
|
+
| Tool | Description |
|
|
206
|
+
|------|-------------|
|
|
207
|
+
| `eval_python` | Execute arbitrary Python in ST's main thread. Locals available: `sublime`, `window`, `view`, `print`. Returns captured stdout in `output` |
|
|
208
|
+
|
|
209
|
+
## Configuration
|
|
210
|
+
|
|
211
|
+
### Port
|
|
212
|
+
|
|
213
|
+
Default is `9500`. To change it, edit `_PORT` in `sublime_mcp.py` and `BASE` in `mcp_server.py`.
|
|
214
|
+
|
|
215
|
+
### Timeout
|
|
216
|
+
|
|
217
|
+
The MCP server waits up to 10 seconds for each HTTP response. Edit `TIMEOUT` in
|
|
218
|
+
`mcp_server.py` if you need longer (e.g. for slow `eval_python` calls).
|
|
219
|
+
|
|
220
|
+
## Security note
|
|
221
|
+
|
|
222
|
+
The HTTP server binds to `127.0.0.1` only and accepts any request without
|
|
223
|
+
authentication. Do not expose port 9500 to a network interface.
|
|
224
|
+
|
|
225
|
+
## Requirements
|
|
226
|
+
|
|
227
|
+
- Sublime Text 4
|
|
228
|
+
- Python 3.10+ (for the MCP server process)
|
|
229
|
+
- `pip install mcp httpx`
|
|
230
|
+
- Terminus package (optional, required only for `send_to_view` on terminal tabs)
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# sublime-mcp
|
|
2
|
+
|
|
3
|
+
<!-- mcp-name: io.github.dpc00/sublime-mcp -->
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
MCP server for Sublime Text 4. Lets Claude Code (or any MCP client) read and
|
|
7
|
+
control a running ST instance via a local HTTP bridge.
|
|
8
|
+
|
|
9
|
+
58 tools covering reading, navigation, editing, searching, build, Terminus
|
|
10
|
+
integration, settings, layout, and live Python scripting.
|
|
11
|
+
|
|
12
|
+
## Architecture
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
Claude Code (MCP client)
|
|
16
|
+
│ stdio / MCP protocol
|
|
17
|
+
▼
|
|
18
|
+
mcp_server.py ← Python process you run outside ST
|
|
19
|
+
│ HTTP 127.0.0.1:9500
|
|
20
|
+
▼
|
|
21
|
+
sublime_mcp.py ← ST plugin, HTTP server on ST's main thread
|
|
22
|
+
│ sublime API
|
|
23
|
+
▼
|
|
24
|
+
Sublime Text 4
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
| File | Role |
|
|
28
|
+
|------|------|
|
|
29
|
+
| `sublime_mcp.py` | ST plugin — runs an HTTP server inside Sublime Text |
|
|
30
|
+
| `mcp_server.py` | MCP server — wraps the HTTP API for MCP clients |
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
### 1. Install the ST plugin
|
|
35
|
+
|
|
36
|
+
Copy `sublime_mcp.py` to your Sublime Text `Packages/User/` folder:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
%APPDATA%\Sublime Text\Packages\User\sublime_mcp.py
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
ST loads it automatically on start (or via `Tools › Developer › New Plugin…`
|
|
43
|
+
then save over it). You should see:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
sublime-mcp: listening on 127.0.0.1:9500
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
in the ST console (`View › Show Console`).
|
|
50
|
+
|
|
51
|
+
### 2. Install the MCP server
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
pip install sublime-mcp
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 3. Register with Claude Code
|
|
58
|
+
|
|
59
|
+
Add to `~/.claude/settings.json`:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"mcpServers": {
|
|
64
|
+
"sublime": {
|
|
65
|
+
"command": "sublime-mcp"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Then restart Claude Code. Tools will appear with the `mcp__sublime__` prefix.
|
|
72
|
+
|
|
73
|
+
Then restart Claude Code. Tools will appear with the `mcp__sublime__` prefix.
|
|
74
|
+
|
|
75
|
+
## Tools
|
|
76
|
+
|
|
77
|
+
### Read / Introspect
|
|
78
|
+
|
|
79
|
+
| Tool | Description |
|
|
80
|
+
|------|-------------|
|
|
81
|
+
| `get_active_file` | Path, full content, cursor line/col, dirty flag, and syntax name |
|
|
82
|
+
| `get_selection` | Current selection(s): text and begin/end line+col for each |
|
|
83
|
+
| `get_cursor_context` | `lines` lines above and below cursor, with 1-based line numbers prepended |
|
|
84
|
+
| `get_open_files` | All files open in the current window (path, name, is_dirty) |
|
|
85
|
+
| `get_project_folders` | Project root folder paths |
|
|
86
|
+
| `get_file_content` | Full content of any already-open file by path |
|
|
87
|
+
| `get_view_content` | Full content of any open tab by name (partial match). Works for Terminus tabs and nameless views |
|
|
88
|
+
| `get_view_size` | Total character count of any open tab. Use to compute offsets before `get_view_chars` |
|
|
89
|
+
| `get_view_chars` | Text at character offsets begin..end (0-based, end exclusive). Clamps to buffer bounds |
|
|
90
|
+
| `get_view_phantoms` | Phantom HTML and extracted plain text from a named view; filters by phantom key |
|
|
91
|
+
| `get_output_panel` | Text content of a named output panel. Omit name for the active panel; `name='exec'` for build output |
|
|
92
|
+
| `get_active_panel` | Active panel id and, if it is an output panel, its content |
|
|
93
|
+
| `get_symbols` | All symbols (functions, classes, etc.) in the active file with line numbers |
|
|
94
|
+
| `lookup_symbol` | Find where a symbol is defined across all open files |
|
|
95
|
+
| `get_project_data` | Raw `.sublime-project` JSON for the current project |
|
|
96
|
+
| `get_variables` | ST build variables: `$file`, `$project_path`, `$platform`, etc. |
|
|
97
|
+
| `get_command_palette` | Command Palette entries from installed `*.sublime-commands` resources; filterable by package, command, or caption |
|
|
98
|
+
| `get_commands` | Runnable command ids from loaded command classes, optionally merged with palette metadata |
|
|
99
|
+
| `get_menu_items` | Menu items from `*.sublime-menu` resources; filterable by menu filename, caption, or command |
|
|
100
|
+
| `get_syntaxes` | All syntax definitions available in ST (name + path) |
|
|
101
|
+
| `get_scope_at_cursor` | Full syntax scope string at the cursor position |
|
|
102
|
+
| `get_word_at_cursor` | Word under the cursor and its line/col |
|
|
103
|
+
| `get_bookmarks` | All bookmarked positions in the active file |
|
|
104
|
+
| `get_line_count` | Total number of lines in the active file |
|
|
105
|
+
| `get_encoding` | Character encoding of the active file |
|
|
106
|
+
| `get_setting` | A ST setting by key. `scope='view'` (default) or `'window'` |
|
|
107
|
+
| `get_layout` | Current window layout (groups, cells) and which files are in each group |
|
|
108
|
+
|
|
109
|
+
### Navigate
|
|
110
|
+
|
|
111
|
+
| Tool | Description |
|
|
112
|
+
|------|-------------|
|
|
113
|
+
| `open_file` | Open a file, optionally jumping to a specific line and column |
|
|
114
|
+
| `goto_line` | Move cursor to line (and optional column) in the active file |
|
|
115
|
+
| `show_panel` | Bring an output panel to the front. Default `name='exec'` for the build panel |
|
|
116
|
+
| `focus_group` | Move focus to a pane group by 0-based index |
|
|
117
|
+
|
|
118
|
+
### Edit
|
|
119
|
+
|
|
120
|
+
| Tool | Description |
|
|
121
|
+
|------|-------------|
|
|
122
|
+
| `replace_selection` | Replace the current selection(s) with text |
|
|
123
|
+
| `replace_lines` | Replace lines begin..end (inclusive, 1-based) in the active file |
|
|
124
|
+
| `insert_snippet` | Insert at the cursor using ST snippet syntax (`$1` for tab stops, etc.) |
|
|
125
|
+
| `duplicate_line` | Duplicate the current line(s) |
|
|
126
|
+
| `toggle_comment` | Toggle line comment, or block comment if `block=True` |
|
|
127
|
+
| `sort_lines` | Sort selected lines, or all lines if nothing is selected |
|
|
128
|
+
| `select_lines` | Select lines begin..end (1-based, inclusive) |
|
|
129
|
+
| `fold_lines` | Fold (collapse) lines begin..end in the active file |
|
|
130
|
+
| `undo` | Undo the last edit |
|
|
131
|
+
| `redo` | Redo the last undone edit |
|
|
132
|
+
| `run_command` | Run any ST command with optional args. `scope='window'` (default) or `'view'` |
|
|
133
|
+
|
|
134
|
+
### Search
|
|
135
|
+
|
|
136
|
+
| Tool | Description |
|
|
137
|
+
|------|-------------|
|
|
138
|
+
| `find_in_file` | Find all occurrences of a pattern in the active file. Returns `{line, col, text}` list |
|
|
139
|
+
| `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) |
|
|
140
|
+
|
|
141
|
+
### File / Project
|
|
142
|
+
|
|
143
|
+
| Tool | Description |
|
|
144
|
+
|------|-------------|
|
|
145
|
+
| `save_file` | Save the active file |
|
|
146
|
+
| `save_all` | Save all open files |
|
|
147
|
+
| `close_file` | Close a file by path, or the active file if path is omitted |
|
|
148
|
+
| `revert_file` | Revert the active file to its last saved state |
|
|
149
|
+
| `add_folder` | Add a folder to the current project (no-op if already present) |
|
|
150
|
+
| `remove_folder` | Remove a folder from the current project by path |
|
|
151
|
+
|
|
152
|
+
### Syntax / Encoding
|
|
153
|
+
|
|
154
|
+
| Tool | Description |
|
|
155
|
+
|------|-------------|
|
|
156
|
+
| `set_syntax` | Set the syntax of the active file by name (case-insensitive partial match) |
|
|
157
|
+
| `set_encoding` | Set the character encoding of the active file (e.g. `'UTF-8'`, `'Western (Windows 1252)'`) |
|
|
158
|
+
|
|
159
|
+
### Settings / Window
|
|
160
|
+
|
|
161
|
+
| Tool | Description |
|
|
162
|
+
|------|-------------|
|
|
163
|
+
| `set_setting` | Set a ST setting by key. `scope='view'` (default) or `'window'` |
|
|
164
|
+
| `toggle_sidebar` | Show or hide the sidebar |
|
|
165
|
+
| `set_layout` | Set the window pane layout. Accepts a ST layout dict with `cols`, `rows`, `cells` |
|
|
166
|
+
| `set_status` | Write a message to ST's status bar |
|
|
167
|
+
|
|
168
|
+
### Build
|
|
169
|
+
|
|
170
|
+
| Tool | Description |
|
|
171
|
+
|------|-------------|
|
|
172
|
+
| `run_build` | Trigger the current build system, or pass `cmd`/`shell_cmd` + `working_dir` for a custom command |
|
|
173
|
+
|
|
174
|
+
### Terminus Integration
|
|
175
|
+
|
|
176
|
+
[Terminus](https://github.com/randy3k/Terminus) is a popular ST terminal package.
|
|
177
|
+
`send_to_view` is Terminus-aware: when targeting a Terminus tab it uses
|
|
178
|
+
`terminus_send_string` to type text into the terminal session rather than
|
|
179
|
+
inserting into a buffer.
|
|
180
|
+
|
|
181
|
+
| Tool | Description |
|
|
182
|
+
|------|-------------|
|
|
183
|
+
| `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 |
|
|
184
|
+
|
|
185
|
+
### Scripting
|
|
186
|
+
|
|
187
|
+
| Tool | Description |
|
|
188
|
+
|------|-------------|
|
|
189
|
+
| `eval_python` | Execute arbitrary Python in ST's main thread. Locals available: `sublime`, `window`, `view`, `print`. Returns captured stdout in `output` |
|
|
190
|
+
|
|
191
|
+
## Configuration
|
|
192
|
+
|
|
193
|
+
### Port
|
|
194
|
+
|
|
195
|
+
Default is `9500`. To change it, edit `_PORT` in `sublime_mcp.py` and `BASE` in `mcp_server.py`.
|
|
196
|
+
|
|
197
|
+
### Timeout
|
|
198
|
+
|
|
199
|
+
The MCP server waits up to 10 seconds for each HTTP response. Edit `TIMEOUT` in
|
|
200
|
+
`mcp_server.py` if you need longer (e.g. for slow `eval_python` calls).
|
|
201
|
+
|
|
202
|
+
## Security note
|
|
203
|
+
|
|
204
|
+
The HTTP server binds to `127.0.0.1` only and accepts any request without
|
|
205
|
+
authentication. Do not expose port 9500 to a network interface.
|
|
206
|
+
|
|
207
|
+
## Requirements
|
|
208
|
+
|
|
209
|
+
- Sublime Text 4
|
|
210
|
+
- Python 3.10+ (for the MCP server process)
|
|
211
|
+
- `pip install mcp httpx`
|
|
212
|
+
- Terminus package (optional, required only for `send_to_view` on terminal tabs)
|
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
"""sublime-mcp — MCP server.
|
|
2
|
+
|
|
3
|
+
Wraps the HTTP API exposed by sublime_mcp.py (the ST plugin) and
|
|
4
|
+
presents it as MCP tools to Claude Code (or any MCP client).
|
|
5
|
+
|
|
6
|
+
Requirements: pip install mcp httpx
|
|
7
|
+
Run: python mcp_server.py
|
|
8
|
+
Register: add to ~/.claude/settings.json mcpServers
|
|
9
|
+
"""
|
|
10
|
+
import httpx
|
|
11
|
+
from mcp.server.fastmcp import FastMCP
|
|
12
|
+
|
|
13
|
+
BASE = "http://127.0.0.1:9500"
|
|
14
|
+
TIMEOUT = 10.0
|
|
15
|
+
|
|
16
|
+
mcp = FastMCP("sublime-mcp")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _get(endpoint: str, **params) -> dict:
|
|
20
|
+
r = httpx.get(f"{BASE}{endpoint}", params=params, timeout=TIMEOUT)
|
|
21
|
+
r.raise_for_status()
|
|
22
|
+
return r.json()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _post(endpoint: str, **body) -> dict:
|
|
26
|
+
r = httpx.post(f"{BASE}{endpoint}", json=body, timeout=TIMEOUT)
|
|
27
|
+
r.raise_for_status()
|
|
28
|
+
return r.json()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ── read ──────────────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@mcp.tool()
|
|
35
|
+
def get_active_file() -> dict:
|
|
36
|
+
"""Return the active file's path, full content, cursor line/col, dirty flag, and syntax name."""
|
|
37
|
+
return _get("/active_file")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@mcp.tool()
|
|
41
|
+
def get_selection() -> dict:
|
|
42
|
+
"""Return the current selection(s): text and begin/end line+col for each."""
|
|
43
|
+
return _get("/selection")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@mcp.tool()
|
|
47
|
+
def get_cursor_context(lines: int = 10) -> dict:
|
|
48
|
+
"""Return `lines` lines above and below the cursor with 1-based line numbers prepended."""
|
|
49
|
+
return _get("/cursor_context", lines=lines)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@mcp.tool()
|
|
53
|
+
def get_open_files() -> dict:
|
|
54
|
+
"""List all files open in the current window (path, name, is_dirty)."""
|
|
55
|
+
return _get("/open_files")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@mcp.tool()
|
|
59
|
+
def get_project_folders() -> dict:
|
|
60
|
+
"""Return the project's root folder paths."""
|
|
61
|
+
return _get("/project_folders")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@mcp.tool()
|
|
65
|
+
def get_file_content(path: str) -> dict:
|
|
66
|
+
"""Return the full content of an already-open file by its path."""
|
|
67
|
+
return _get("/file_content", path=path)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@mcp.tool()
|
|
71
|
+
def get_view_content(name: str = "") -> dict:
|
|
72
|
+
"""Return the full content of any open tab by name (partial match, case-insensitive).
|
|
73
|
+
Works for Terminus tabs and other nameless views that have no file path.
|
|
74
|
+
Omit name to read the active view."""
|
|
75
|
+
return _get("/view_content", name=name)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@mcp.tool()
|
|
79
|
+
def get_view_size(name: str = "") -> dict:
|
|
80
|
+
"""Return the total character count of any open tab by name (partial match, case-insensitive).
|
|
81
|
+
Use before get_view_chars to compute offsets — e.g. begin=size-5000, end=size for the tail.
|
|
82
|
+
Omit name for the active view."""
|
|
83
|
+
return _get("/view_size", name=name)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@mcp.tool()
|
|
87
|
+
def get_view_chars(begin: int, end: int, name: str = "") -> dict:
|
|
88
|
+
"""Return text at character offsets begin..end (0-based, end exclusive) from any open tab.
|
|
89
|
+
Works for Terminus tabs and any other view. Clamps to buffer bounds automatically.
|
|
90
|
+
Use get_view_size first, then e.g. begin=size-5000, end=size to read the last 5000 chars.
|
|
91
|
+
Omit name for the active view."""
|
|
92
|
+
return _get("/view_chars", name=name, begin=begin, end=end)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@mcp.tool()
|
|
96
|
+
def get_view_phantoms(name: str = "", key: str = "") -> dict:
|
|
97
|
+
"""Return phantom HTML and extracted text from a view by name.
|
|
98
|
+
If key is omitted, defaults to the common 'pybackup' phantom key."""
|
|
99
|
+
return _get("/view_phantoms", name=name, key=key)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@mcp.tool()
|
|
103
|
+
def send_to_view(text: str, name: str = "") -> dict:
|
|
104
|
+
"""Send a string to any open tab by name (partial match, case-insensitive).
|
|
105
|
+
For Terminus tabs this types the text into the terminal as if the user typed it.
|
|
106
|
+
Include a trailing newline (\\n) to execute a command.
|
|
107
|
+
Omit name to target the active view."""
|
|
108
|
+
return _post("/send_to_view", text=text, name=name)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@mcp.tool()
|
|
112
|
+
def get_output_panel(name: str = "") -> dict:
|
|
113
|
+
"""Return the text content of an output panel.
|
|
114
|
+
If name is omitted, read the active output panel. Use name='exec' for build output.
|
|
115
|
+
"""
|
|
116
|
+
return _get("/output_panel", name=name)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@mcp.tool()
|
|
120
|
+
def get_symbols() -> dict:
|
|
121
|
+
"""Return all symbols (functions, classes, etc.) in the active file with line numbers."""
|
|
122
|
+
return _get("/symbols")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@mcp.tool()
|
|
126
|
+
def lookup_symbol(symbol: str) -> dict:
|
|
127
|
+
"""Find where a symbol is defined across all open files."""
|
|
128
|
+
return _get("/lookup_symbol", symbol=symbol)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@mcp.tool()
|
|
132
|
+
def get_project_data() -> dict:
|
|
133
|
+
"""Return the raw .sublime-project JSON data for the current project."""
|
|
134
|
+
return _get("/project_data")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@mcp.tool()
|
|
138
|
+
def add_folder(path: str) -> dict:
|
|
139
|
+
"""Add a folder to the current project."""
|
|
140
|
+
data = _get("/project_data").get("project_data") or {}
|
|
141
|
+
folders = data.get("folders", [])
|
|
142
|
+
if not any(f.get("path") == path for f in folders):
|
|
143
|
+
folders.append({"path": path})
|
|
144
|
+
data["folders"] = folders
|
|
145
|
+
return _post("/set_project_data", data=data)
|
|
146
|
+
return {"ok": True, "note": "already present"}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@mcp.tool()
|
|
150
|
+
def remove_folder(path: str) -> dict:
|
|
151
|
+
"""Remove a folder from the current project by path."""
|
|
152
|
+
data = _get("/project_data").get("project_data") or {}
|
|
153
|
+
folders = data.get("folders", [])
|
|
154
|
+
new_folders = [f for f in folders if f.get("path") != path]
|
|
155
|
+
if len(new_folders) == len(folders):
|
|
156
|
+
return {"ok": False, "note": "folder not found"}
|
|
157
|
+
data["folders"] = new_folders
|
|
158
|
+
return _post("/set_project_data", data=data)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@mcp.tool()
|
|
162
|
+
def get_variables() -> dict:
|
|
163
|
+
"""Return Sublime Text's build variables: $file, $project_path, $platform, etc."""
|
|
164
|
+
return _get("/variables")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# ── navigate ──────────────────────────────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@mcp.tool()
|
|
171
|
+
def open_file(path: str, line: int = 0, col: int = 0) -> dict:
|
|
172
|
+
"""Open a file in Sublime Text, optionally jumping to a specific line and column."""
|
|
173
|
+
return _post("/open_file", path=path, line=line, col=col)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@mcp.tool()
|
|
177
|
+
def goto_line(line: int, col: int = 1) -> dict:
|
|
178
|
+
"""Move the cursor to a line (and optional column) in the active file."""
|
|
179
|
+
return _post("/goto_line", line=line, col=col)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@mcp.tool()
|
|
183
|
+
def show_panel(name: str = "exec") -> dict:
|
|
184
|
+
"""Bring an output panel to the front. Use name='exec' for the build panel."""
|
|
185
|
+
return _post("/show_panel", name=name)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# ── edit ──────────────────────────────────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@mcp.tool()
|
|
192
|
+
def replace_selection(text: str) -> dict:
|
|
193
|
+
"""Replace the current selection(s) with text."""
|
|
194
|
+
return _post("/replace_selection", text=text)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@mcp.tool()
|
|
198
|
+
def replace_lines(begin: int, end: int, text: str, path: str = "") -> dict:
|
|
199
|
+
"""Replace lines begin through end (inclusive, 1-based) in the active file with text.
|
|
200
|
+
Pass path to target a specific open file regardless of which tab is focused."""
|
|
201
|
+
return _post("/replace_lines", begin=begin, end=end, text=text, path=path)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@mcp.tool()
|
|
205
|
+
def run_command(command: str, args: dict = None, scope: str = "window") -> dict:
|
|
206
|
+
"""Run any Sublime Text command. scope='window' (default) or 'view'."""
|
|
207
|
+
return _post("/run_command", command=command, args=args or {}, scope=scope)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# ── build ─────────────────────────────────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@mcp.tool()
|
|
214
|
+
def run_build(cmd: list = None, shell_cmd: str = None, working_dir: str = "") -> dict:
|
|
215
|
+
"""Trigger the current build system, or pass cmd/shell_cmd to run a specific command."""
|
|
216
|
+
body = {}
|
|
217
|
+
if cmd:
|
|
218
|
+
body["cmd"] = cmd
|
|
219
|
+
if shell_cmd:
|
|
220
|
+
body["shell_cmd"] = shell_cmd
|
|
221
|
+
if working_dir:
|
|
222
|
+
body["working_dir"] = working_dir
|
|
223
|
+
return _post("/run_build", **body)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
# ── misc ──────────────────────────────────────────────────────────────────────
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@mcp.tool()
|
|
230
|
+
def set_status(value: str, key: str = "sublime_mcp") -> dict:
|
|
231
|
+
"""Write a message to Sublime Text's status bar."""
|
|
232
|
+
return _post("/set_status", key=key, value=value)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
# ── file ops ──────────────────────────────────────────────────────────────────
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@mcp.tool()
|
|
239
|
+
def save_file(path: str = "") -> dict:
|
|
240
|
+
"""Save a file. Pass path to save a specific open file; omit path to save the active file."""
|
|
241
|
+
return _post("/save_file", path=path) if path else _post("/save_file")
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@mcp.tool()
|
|
245
|
+
def save_all() -> dict:
|
|
246
|
+
"""Save all open files."""
|
|
247
|
+
return _post("/save_all")
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@mcp.tool()
|
|
251
|
+
def close_file(path: str = "") -> dict:
|
|
252
|
+
"""Close a file by path, or close the active file if path is omitted."""
|
|
253
|
+
return _post("/close_file", path=path)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@mcp.tool()
|
|
257
|
+
def revert_file() -> dict:
|
|
258
|
+
"""Revert the active file to its last saved state, discarding unsaved changes."""
|
|
259
|
+
return _post("/revert_file")
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
# ── edit ops ──────────────────────────────────────────────────────────────────
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@mcp.tool()
|
|
266
|
+
def undo() -> dict:
|
|
267
|
+
"""Undo the last edit in the active file."""
|
|
268
|
+
return _post("/undo")
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
@mcp.tool()
|
|
272
|
+
def redo() -> dict:
|
|
273
|
+
"""Redo the last undone edit in the active file."""
|
|
274
|
+
return _post("/redo")
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@mcp.tool()
|
|
278
|
+
def duplicate_line() -> dict:
|
|
279
|
+
"""Duplicate the current line(s) in the active file."""
|
|
280
|
+
return _post("/duplicate_line")
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
@mcp.tool()
|
|
284
|
+
def toggle_comment(block: bool = False) -> dict:
|
|
285
|
+
"""Toggle line comment (or block comment if block=True) on the current selection."""
|
|
286
|
+
return _post("/toggle_comment", block=block)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@mcp.tool()
|
|
290
|
+
def sort_lines(case_sensitive: bool = False) -> dict:
|
|
291
|
+
"""Sort the selected lines (or all lines if nothing is selected)."""
|
|
292
|
+
return _post("/sort_lines", case_sensitive=case_sensitive)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
@mcp.tool()
|
|
296
|
+
def select_lines(begin: int, end: int = 0) -> dict:
|
|
297
|
+
"""Select lines begin through end (1-based, inclusive). end defaults to begin."""
|
|
298
|
+
return _post("/select_lines", begin=begin, end=end or begin)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@mcp.tool()
|
|
302
|
+
def fold_lines(begin: int, end: int) -> dict:
|
|
303
|
+
"""Fold (collapse) lines begin through end (1-based) in the active file."""
|
|
304
|
+
return _post("/fold_lines", begin=begin, end=end)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
@mcp.tool()
|
|
308
|
+
def insert_snippet(contents: str) -> dict:
|
|
309
|
+
"""Insert a snippet at the cursor using Sublime Text's snippet syntax (e.g. $1 for tab stops)."""
|
|
310
|
+
return _post("/insert_snippet", contents=contents)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
# ── search ────────────────────────────────────────────────────────────────────
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
@mcp.tool()
|
|
317
|
+
def find_in_file(
|
|
318
|
+
pattern: str, case_sensitive: bool = False, regex: bool = False
|
|
319
|
+
) -> dict:
|
|
320
|
+
"""Find all occurrences of pattern in the active file. Returns list of {line, col, text}."""
|
|
321
|
+
return _post(
|
|
322
|
+
"/find_in_file", pattern=pattern, case_sensitive=case_sensitive, regex=regex
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
@mcp.tool()
|
|
327
|
+
def find_in_files(
|
|
328
|
+
pattern: str,
|
|
329
|
+
folders: list = None,
|
|
330
|
+
case_sensitive: bool = False,
|
|
331
|
+
regex: bool = False,
|
|
332
|
+
max_results: int = 200,
|
|
333
|
+
) -> dict:
|
|
334
|
+
"""Search for pattern across project folders (or the supplied folder list).
|
|
335
|
+
Skips .git, __pycache__, node_modules, .venv. Returns list of {path, line, match}.
|
|
336
|
+
"""
|
|
337
|
+
body = dict(
|
|
338
|
+
pattern=pattern,
|
|
339
|
+
case_sensitive=case_sensitive,
|
|
340
|
+
regex=regex,
|
|
341
|
+
max_results=max_results,
|
|
342
|
+
)
|
|
343
|
+
if folders:
|
|
344
|
+
body["folders"] = folders
|
|
345
|
+
return _post("/find_in_files", **body)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
# ── syntax / encoding ─────────────────────────────────────────────────────────
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
@mcp.tool()
|
|
352
|
+
def get_syntaxes() -> dict:
|
|
353
|
+
"""List all syntax definitions available in Sublime Text (name + path)."""
|
|
354
|
+
return _get("/syntaxes")
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
@mcp.tool()
|
|
358
|
+
def get_command_palette(
|
|
359
|
+
package: str = "", command: str = "", caption: str = ""
|
|
360
|
+
) -> dict:
|
|
361
|
+
"""List Command Palette entries from installed *.sublime-commands resources.
|
|
362
|
+
Optional filters: package, command id, or caption substring."""
|
|
363
|
+
return _get("/command_palette", package=package, command=command, caption=caption)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
@mcp.tool()
|
|
367
|
+
def get_commands(
|
|
368
|
+
package: str = "", command: str = "", include_palette: bool = True
|
|
369
|
+
) -> dict:
|
|
370
|
+
"""List runnable Sublime command ids from loaded command classes, optionally enriched
|
|
371
|
+
with matching Command Palette entries from installed packages."""
|
|
372
|
+
return _get(
|
|
373
|
+
"/commands",
|
|
374
|
+
package=package,
|
|
375
|
+
command=command,
|
|
376
|
+
include_palette=str(include_palette).lower(),
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
@mcp.tool()
|
|
381
|
+
def get_menu_items(menu: str = "", caption: str = "", command: str = "") -> dict:
|
|
382
|
+
"""List installed menu items from *.sublime-menu resources.
|
|
383
|
+
Optional filters: menu filename, caption substring, or command id substring."""
|
|
384
|
+
return _get("/menu_items", menu=menu, caption=caption, command=command)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
@mcp.tool()
|
|
388
|
+
def get_active_panel() -> dict:
|
|
389
|
+
"""Return the active panel id and, if it is an output panel, its content."""
|
|
390
|
+
return _get("/active_panel")
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
@mcp.tool()
|
|
394
|
+
def set_syntax(name: str) -> dict:
|
|
395
|
+
"""Set the syntax of the active file by name (case-insensitive partial match is fine)."""
|
|
396
|
+
return _post("/set_syntax", name=name)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
@mcp.tool()
|
|
400
|
+
def get_encoding() -> dict:
|
|
401
|
+
"""Return the character encoding of the active file."""
|
|
402
|
+
return _get("/encoding")
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
@mcp.tool()
|
|
406
|
+
def set_encoding(encoding: str) -> dict:
|
|
407
|
+
"""Set the character encoding of the active file (e.g. 'UTF-8', 'Western (Windows 1252)')."""
|
|
408
|
+
return _post("/set_encoding", encoding=encoding)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
# ── cursor / scope ────────────────────────────────────────────────────────────
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
@mcp.tool()
|
|
415
|
+
def get_scope_at_cursor() -> dict:
|
|
416
|
+
"""Return the full syntax scope string at the cursor position."""
|
|
417
|
+
return _get("/scope_at_cursor")
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
@mcp.tool()
|
|
421
|
+
def get_word_at_cursor() -> dict:
|
|
422
|
+
"""Return the word under the cursor and its line/col."""
|
|
423
|
+
return _get("/word_at_cursor")
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
@mcp.tool()
|
|
427
|
+
def get_bookmarks() -> dict:
|
|
428
|
+
"""Return all bookmarked positions in the active file."""
|
|
429
|
+
return _get("/bookmarks")
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
@mcp.tool()
|
|
433
|
+
def get_line_count() -> dict:
|
|
434
|
+
"""Return the total number of lines in the active file."""
|
|
435
|
+
return _get("/line_count")
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
# ── settings ──────────────────────────────────────────────────────────────────
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
@mcp.tool()
|
|
442
|
+
def get_setting(key: str, scope: str = "view") -> dict:
|
|
443
|
+
"""Get a Sublime Text setting by key. scope='view' (default) or 'window'."""
|
|
444
|
+
return _post("/get_setting", key=key, scope=scope)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
@mcp.tool()
|
|
448
|
+
def set_setting(key: str, value, scope: str = "view") -> dict:
|
|
449
|
+
"""Set a Sublime Text setting by key. scope='view' (default) or 'window'."""
|
|
450
|
+
return _post("/set_setting", key=key, value=value, scope=scope)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
# ── window / layout ───────────────────────────────────────────────────────────
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
@mcp.tool()
|
|
457
|
+
def toggle_sidebar() -> dict:
|
|
458
|
+
"""Show or hide the Sublime Text sidebar."""
|
|
459
|
+
return _post("/toggle_sidebar")
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
@mcp.tool()
|
|
463
|
+
def get_layout() -> dict:
|
|
464
|
+
"""Return the current window layout (groups, cells) and which files are in each group."""
|
|
465
|
+
return _get("/layout")
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
@mcp.tool()
|
|
469
|
+
def focus_group(group: int) -> dict:
|
|
470
|
+
"""Move focus to a pane group by 0-based index."""
|
|
471
|
+
return _post("/focus_group", group=group)
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
@mcp.tool()
|
|
475
|
+
def set_layout(layout: dict) -> dict:
|
|
476
|
+
"""Set the window pane layout. layout must be a ST layout dict with cols, rows, cells keys."""
|
|
477
|
+
return _post("/set_layout", layout=layout)
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
# ── scripting ─────────────────────────────────────────────────────────────────
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
@mcp.tool()
|
|
484
|
+
def eval_python(code: str) -> dict:
|
|
485
|
+
"""Execute arbitrary Python in Sublime Text's main thread.
|
|
486
|
+
Locals: sublime, window, view, print. Returns captured stdout in 'output'."""
|
|
487
|
+
return _post("/eval_python", code=code)
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def main():
|
|
491
|
+
mcp.run()
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
if __name__ == "__main__":
|
|
495
|
+
main()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sublime-mcp"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "MCP server for Sublime Text 4 — lets Claude Code read and control a running ST instance"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
dependencies = ["mcp", "httpx"]
|
|
13
|
+
keywords = ["mcp", "sublime-text", "claude", "ai", "editor"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Topic :: Text Editors",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[[project.authors]]
|
|
22
|
+
name = "Donald Chitester"
|
|
23
|
+
email = "donaldchitester@gmail.com"
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Homepage = "https://github.com/dpc00/sublime-mcp"
|
|
27
|
+
Repository = "https://github.com/dpc00/sublime-mcp"
|
|
28
|
+
|
|
29
|
+
[project.scripts]
|
|
30
|
+
sublime-mcp = "mcp_server:main"
|
|
31
|
+
|
|
32
|
+
[tool.setuptools]
|
|
33
|
+
py-modules = ["mcp_server"]
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sublime-mcp
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: MCP server for Sublime Text 4 — lets Claude Code read and control a running ST instance
|
|
5
|
+
Author-email: Donald Chitester <donaldchitester@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/dpc00/sublime-mcp
|
|
8
|
+
Project-URL: Repository, https://github.com/dpc00/sublime-mcp
|
|
9
|
+
Keywords: mcp,sublime-text,claude,ai,editor
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Text Editors
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: mcp
|
|
17
|
+
Requires-Dist: httpx
|
|
18
|
+
|
|
19
|
+
# sublime-mcp
|
|
20
|
+
|
|
21
|
+
<!-- mcp-name: io.github.dpc00/sublime-mcp -->
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
MCP server for Sublime Text 4. Lets Claude Code (or any MCP client) read and
|
|
25
|
+
control a running ST instance via a local HTTP bridge.
|
|
26
|
+
|
|
27
|
+
58 tools covering reading, navigation, editing, searching, build, Terminus
|
|
28
|
+
integration, settings, layout, and live Python scripting.
|
|
29
|
+
|
|
30
|
+
## Architecture
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
Claude Code (MCP client)
|
|
34
|
+
│ stdio / MCP protocol
|
|
35
|
+
▼
|
|
36
|
+
mcp_server.py ← Python process you run outside ST
|
|
37
|
+
│ HTTP 127.0.0.1:9500
|
|
38
|
+
▼
|
|
39
|
+
sublime_mcp.py ← ST plugin, HTTP server on ST's main thread
|
|
40
|
+
│ sublime API
|
|
41
|
+
▼
|
|
42
|
+
Sublime Text 4
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
| File | Role |
|
|
46
|
+
|------|------|
|
|
47
|
+
| `sublime_mcp.py` | ST plugin — runs an HTTP server inside Sublime Text |
|
|
48
|
+
| `mcp_server.py` | MCP server — wraps the HTTP API for MCP clients |
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
### 1. Install the ST plugin
|
|
53
|
+
|
|
54
|
+
Copy `sublime_mcp.py` to your Sublime Text `Packages/User/` folder:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
%APPDATA%\Sublime Text\Packages\User\sublime_mcp.py
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
ST loads it automatically on start (or via `Tools › Developer › New Plugin…`
|
|
61
|
+
then save over it). You should see:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
sublime-mcp: listening on 127.0.0.1:9500
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
in the ST console (`View › Show Console`).
|
|
68
|
+
|
|
69
|
+
### 2. Install the MCP server
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
pip install sublime-mcp
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 3. Register with Claude Code
|
|
76
|
+
|
|
77
|
+
Add to `~/.claude/settings.json`:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"mcpServers": {
|
|
82
|
+
"sublime": {
|
|
83
|
+
"command": "sublime-mcp"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Then restart Claude Code. Tools will appear with the `mcp__sublime__` prefix.
|
|
90
|
+
|
|
91
|
+
Then restart Claude Code. Tools will appear with the `mcp__sublime__` prefix.
|
|
92
|
+
|
|
93
|
+
## Tools
|
|
94
|
+
|
|
95
|
+
### Read / Introspect
|
|
96
|
+
|
|
97
|
+
| Tool | Description |
|
|
98
|
+
|------|-------------|
|
|
99
|
+
| `get_active_file` | Path, full content, cursor line/col, dirty flag, and syntax name |
|
|
100
|
+
| `get_selection` | Current selection(s): text and begin/end line+col for each |
|
|
101
|
+
| `get_cursor_context` | `lines` lines above and below cursor, with 1-based line numbers prepended |
|
|
102
|
+
| `get_open_files` | All files open in the current window (path, name, is_dirty) |
|
|
103
|
+
| `get_project_folders` | Project root folder paths |
|
|
104
|
+
| `get_file_content` | Full content of any already-open file by path |
|
|
105
|
+
| `get_view_content` | Full content of any open tab by name (partial match). Works for Terminus tabs and nameless views |
|
|
106
|
+
| `get_view_size` | Total character count of any open tab. Use to compute offsets before `get_view_chars` |
|
|
107
|
+
| `get_view_chars` | Text at character offsets begin..end (0-based, end exclusive). Clamps to buffer bounds |
|
|
108
|
+
| `get_view_phantoms` | Phantom HTML and extracted plain text from a named view; filters by phantom key |
|
|
109
|
+
| `get_output_panel` | Text content of a named output panel. Omit name for the active panel; `name='exec'` for build output |
|
|
110
|
+
| `get_active_panel` | Active panel id and, if it is an output panel, its content |
|
|
111
|
+
| `get_symbols` | All symbols (functions, classes, etc.) in the active file with line numbers |
|
|
112
|
+
| `lookup_symbol` | Find where a symbol is defined across all open files |
|
|
113
|
+
| `get_project_data` | Raw `.sublime-project` JSON for the current project |
|
|
114
|
+
| `get_variables` | ST build variables: `$file`, `$project_path`, `$platform`, etc. |
|
|
115
|
+
| `get_command_palette` | Command Palette entries from installed `*.sublime-commands` resources; filterable by package, command, or caption |
|
|
116
|
+
| `get_commands` | Runnable command ids from loaded command classes, optionally merged with palette metadata |
|
|
117
|
+
| `get_menu_items` | Menu items from `*.sublime-menu` resources; filterable by menu filename, caption, or command |
|
|
118
|
+
| `get_syntaxes` | All syntax definitions available in ST (name + path) |
|
|
119
|
+
| `get_scope_at_cursor` | Full syntax scope string at the cursor position |
|
|
120
|
+
| `get_word_at_cursor` | Word under the cursor and its line/col |
|
|
121
|
+
| `get_bookmarks` | All bookmarked positions in the active file |
|
|
122
|
+
| `get_line_count` | Total number of lines in the active file |
|
|
123
|
+
| `get_encoding` | Character encoding of the active file |
|
|
124
|
+
| `get_setting` | A ST setting by key. `scope='view'` (default) or `'window'` |
|
|
125
|
+
| `get_layout` | Current window layout (groups, cells) and which files are in each group |
|
|
126
|
+
|
|
127
|
+
### Navigate
|
|
128
|
+
|
|
129
|
+
| Tool | Description |
|
|
130
|
+
|------|-------------|
|
|
131
|
+
| `open_file` | Open a file, optionally jumping to a specific line and column |
|
|
132
|
+
| `goto_line` | Move cursor to line (and optional column) in the active file |
|
|
133
|
+
| `show_panel` | Bring an output panel to the front. Default `name='exec'` for the build panel |
|
|
134
|
+
| `focus_group` | Move focus to a pane group by 0-based index |
|
|
135
|
+
|
|
136
|
+
### Edit
|
|
137
|
+
|
|
138
|
+
| Tool | Description |
|
|
139
|
+
|------|-------------|
|
|
140
|
+
| `replace_selection` | Replace the current selection(s) with text |
|
|
141
|
+
| `replace_lines` | Replace lines begin..end (inclusive, 1-based) in the active file |
|
|
142
|
+
| `insert_snippet` | Insert at the cursor using ST snippet syntax (`$1` for tab stops, etc.) |
|
|
143
|
+
| `duplicate_line` | Duplicate the current line(s) |
|
|
144
|
+
| `toggle_comment` | Toggle line comment, or block comment if `block=True` |
|
|
145
|
+
| `sort_lines` | Sort selected lines, or all lines if nothing is selected |
|
|
146
|
+
| `select_lines` | Select lines begin..end (1-based, inclusive) |
|
|
147
|
+
| `fold_lines` | Fold (collapse) lines begin..end in the active file |
|
|
148
|
+
| `undo` | Undo the last edit |
|
|
149
|
+
| `redo` | Redo the last undone edit |
|
|
150
|
+
| `run_command` | Run any ST command with optional args. `scope='window'` (default) or `'view'` |
|
|
151
|
+
|
|
152
|
+
### Search
|
|
153
|
+
|
|
154
|
+
| Tool | Description |
|
|
155
|
+
|------|-------------|
|
|
156
|
+
| `find_in_file` | Find all occurrences of a pattern in the active file. Returns `{line, col, text}` list |
|
|
157
|
+
| `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) |
|
|
158
|
+
|
|
159
|
+
### File / Project
|
|
160
|
+
|
|
161
|
+
| Tool | Description |
|
|
162
|
+
|------|-------------|
|
|
163
|
+
| `save_file` | Save the active file |
|
|
164
|
+
| `save_all` | Save all open files |
|
|
165
|
+
| `close_file` | Close a file by path, or the active file if path is omitted |
|
|
166
|
+
| `revert_file` | Revert the active file to its last saved state |
|
|
167
|
+
| `add_folder` | Add a folder to the current project (no-op if already present) |
|
|
168
|
+
| `remove_folder` | Remove a folder from the current project by path |
|
|
169
|
+
|
|
170
|
+
### Syntax / Encoding
|
|
171
|
+
|
|
172
|
+
| Tool | Description |
|
|
173
|
+
|------|-------------|
|
|
174
|
+
| `set_syntax` | Set the syntax of the active file by name (case-insensitive partial match) |
|
|
175
|
+
| `set_encoding` | Set the character encoding of the active file (e.g. `'UTF-8'`, `'Western (Windows 1252)'`) |
|
|
176
|
+
|
|
177
|
+
### Settings / Window
|
|
178
|
+
|
|
179
|
+
| Tool | Description |
|
|
180
|
+
|------|-------------|
|
|
181
|
+
| `set_setting` | Set a ST setting by key. `scope='view'` (default) or `'window'` |
|
|
182
|
+
| `toggle_sidebar` | Show or hide the sidebar |
|
|
183
|
+
| `set_layout` | Set the window pane layout. Accepts a ST layout dict with `cols`, `rows`, `cells` |
|
|
184
|
+
| `set_status` | Write a message to ST's status bar |
|
|
185
|
+
|
|
186
|
+
### Build
|
|
187
|
+
|
|
188
|
+
| Tool | Description |
|
|
189
|
+
|------|-------------|
|
|
190
|
+
| `run_build` | Trigger the current build system, or pass `cmd`/`shell_cmd` + `working_dir` for a custom command |
|
|
191
|
+
|
|
192
|
+
### Terminus Integration
|
|
193
|
+
|
|
194
|
+
[Terminus](https://github.com/randy3k/Terminus) is a popular ST terminal package.
|
|
195
|
+
`send_to_view` is Terminus-aware: when targeting a Terminus tab it uses
|
|
196
|
+
`terminus_send_string` to type text into the terminal session rather than
|
|
197
|
+
inserting into a buffer.
|
|
198
|
+
|
|
199
|
+
| Tool | Description |
|
|
200
|
+
|------|-------------|
|
|
201
|
+
| `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 |
|
|
202
|
+
|
|
203
|
+
### Scripting
|
|
204
|
+
|
|
205
|
+
| Tool | Description |
|
|
206
|
+
|------|-------------|
|
|
207
|
+
| `eval_python` | Execute arbitrary Python in ST's main thread. Locals available: `sublime`, `window`, `view`, `print`. Returns captured stdout in `output` |
|
|
208
|
+
|
|
209
|
+
## Configuration
|
|
210
|
+
|
|
211
|
+
### Port
|
|
212
|
+
|
|
213
|
+
Default is `9500`. To change it, edit `_PORT` in `sublime_mcp.py` and `BASE` in `mcp_server.py`.
|
|
214
|
+
|
|
215
|
+
### Timeout
|
|
216
|
+
|
|
217
|
+
The MCP server waits up to 10 seconds for each HTTP response. Edit `TIMEOUT` in
|
|
218
|
+
`mcp_server.py` if you need longer (e.g. for slow `eval_python` calls).
|
|
219
|
+
|
|
220
|
+
## Security note
|
|
221
|
+
|
|
222
|
+
The HTTP server binds to `127.0.0.1` only and accepts any request without
|
|
223
|
+
authentication. Do not expose port 9500 to a network interface.
|
|
224
|
+
|
|
225
|
+
## Requirements
|
|
226
|
+
|
|
227
|
+
- Sublime Text 4
|
|
228
|
+
- Python 3.10+ (for the MCP server process)
|
|
229
|
+
- `pip install mcp httpx`
|
|
230
|
+
- Terminus package (optional, required only for `send_to_view` on terminal tabs)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
mcp_server.py
|
|
3
|
+
pyproject.toml
|
|
4
|
+
sublime_mcp.egg-info/PKG-INFO
|
|
5
|
+
sublime_mcp.egg-info/SOURCES.txt
|
|
6
|
+
sublime_mcp.egg-info/dependency_links.txt
|
|
7
|
+
sublime_mcp.egg-info/entry_points.txt
|
|
8
|
+
sublime_mcp.egg-info/requires.txt
|
|
9
|
+
sublime_mcp.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mcp_server
|