AI Bridge
AI model MCP connector for Sublime Text. Search functions and edit them piecemeal using context friendly lightweight get/set commands. Write content to, and from, your current selection. Run Sublime Text commands like sort current selection.
Details
Installs
- Total 16
- Win 10
- Mac 3
- Linux 3
| Jun 13 | Jun 12 | Jun 11 | Jun 10 | Jun 9 | Jun 8 | Jun 7 | Jun 6 | Jun 5 | Jun 4 | Jun 3 | Jun 2 | Jun 1 | May 31 | May 30 | May 29 | May 28 | May 27 | May 26 | May 25 | May 24 | May 23 | May 22 | May 21 | May 20 | May 19 | May 18 | May 17 | May 16 | May 15 | May 14 | May 13 | May 12 | May 11 | May 10 | May 9 | May 8 | May 7 | May 6 | May 5 | May 4 | May 3 | May 2 | May 1 | Apr 30 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Windows | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | 1 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| Mac | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| Linux | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Readme
- Source
- raw.githubusercontent.com
AI Bridge
An MCP (Model Context Protocol) server, hosted inside Sublime Text as a plugin, that lets an LLM read and edit code through Sublime's own APIs — symbol index, syntax-aware definition extraction, project search, and atomic in-buffer edits.
The LLM gets the same “go to definition” and “find references” power that Sublime gives you, plus the ability to read and rewrite whole functions while leaving ST's undo stack intact.
There is no separate server process to start. Drop the package into
Sublime, and the MCP endpoint at http://127.0.0.1:8765/mcp comes up with it.
Video Demo - Practical Use and Setup
https://github.com/user-attachments/assets/75c83736-1e69-49b7-bb53-dee496b22bf0
What it does
Tools are grouped by domain. Every coordinate is 1-indexed, matching Sublime's status bar.
Project structure
| Tool | What it does |
|---|---|
list_folders_in_project(glob?, max_results?) |
Full recursive folder list for the active project — roots plus all subfolders — honoring Sublime's folder excludes (.git, node_modules, etc. skipped). glob is an optional path glob scoping which folders return, matched relative to each root — same syntax as find_files_in_project (application/**, src/*, **/migrations). A scoped glob excludes the roots from folders, so read anchors from roots. Capped at max_results (default 200). Returns {roots, folders, project_file, count, truncated}. |
find_files_in_project(pattern, regex?, glob?, max_results?) |
Find files across the project. pattern is fnmatched against the basename (or treated as a regex if regex=true); glob is a path glob (application/**, src/**/*.test.ts) restricting which directories to search. Capped at max_results (default 1000). Returns {files, count, truncated}. |
File content
| Tool | What it does |
|---|---|
get_file_content(file_path, start_line_number, stop_line_number?) |
Read a line-range slice of a file from disk. 1-indexed, inclusive. Omit stop_line_number to read through end-of-file. |
set_file_content(file_path, content, start_line_number, stop_line_number?, save?) |
Replace a contiguous range of lines. Omit stop_line_number to replace through end-of-file. Single ST undo entry. |
apply_edits_to_file(file_path, edits[], save?) |
Apply many edits to one file as a single ST undo entry — all-or-nothing. Each entry in edits is {type:"line_range", start_line, stop_line?, content}, {type:"row_col_range", start_row, start_column, end_row, end_column, content}, or {type:"find", find, replace, regex?, all?}. All edit positions/patterns are resolved against the file's ORIGINAL content (no line-drift accounting needed); overlapping edits are rejected up front. Returns {ok, file_path, edits_applied, dirty, details}. |
Function / symbol
“Function” is shorthand. These tools work on any symbol kind Sublime indexes — functions, methods, classes, namespaces, structs, etc. The result
kindfield tells you the actual type.
| Tool | What it does |
|---|---|
find_function_in_project(symbol) |
Find DECLARATION sites for symbol. |
find_function_in_open_files(symbol) |
Same as above, restricted to currently open buffers. |
find_function_usages_in_project(symbol) |
Find CALL SITES / reference sites for symbol. |
list_functions_in_file(file_path) |
List all symbols in a single file. |
get_function_content(file_path, name, row?) |
Return the full source text of a definition (signature through closing brace). |
set_function_content(file_path, name, new_text, save?, row?) |
Replace an entire definition with new_text. Single undo entry. |
get_symbol_definition_at(file_path, row, column) |
POSITION-based 'Go to Definition' via the LSP package, on any symbol kind — the language server's scope-aware resolution gives a single precise answer (vs. find_function_in_project's NAME-based, possibly-ambiguous matches). Returns {file_path, row, column, locations: [{file_path, row, column, end_row, end_column}, ...], count, lsp_available}. Returns lsp_available: false with a note if no LSP session can serve the request. |
get_symbol_info_at(file_path, row, column) |
POSITION-based hover info via the LSP package — type signature, docstring, deprecation notes — for any symbol kind. Cheaper than fetching the full function body when you only need the signature. Returns {file_path, row, column, contents (markdown string or null), lsp_available}. Returns lsp_available: false with a note if no LSP session can serve it. |
Text search
| Tool | What it does |
|---|---|
search_text_in_project(pattern, regex?, case_sensitive?, glob?, max_results?) |
Grep file contents across the project (on-disk). |
search_text_in_open_files(pattern, regex?, case_sensitive?, max_results?) |
Grep across the in-memory buffer text of currently open views — sees unsaved edits. |
Editor state
| Tool | What it does |
|---|---|
get_diagnostics(file_path?) |
LSP-derived diagnostics (errors, warnings, info, hints) for file_path, or every file the LSP package is tracking when omitted. Same content as the LSP panel and inline annotations. Each entry has file_path, 1-indexed row/column range, message, severity, source (LSP server name), and code; counts summarizes severity totals. Requires the LSP package; returns lsp_available: false with a note when it isn't installed. |
get_current_selections() |
Return the active view's cursor/selection state with the file path and 1-indexed line ranges. |
run_sublime_command(command, args?, file_path?) |
Run any Sublime Text command (formatters, sort, custom plugins). Escape hatch. |
Git
Thin wrappers around the git binary, scoped to the active project's first folder (or to the file's enclosing repository when a path/file_path is supplied). git must be on PATH. Args that look like options (start with -) are rejected to prevent option-injection.
| Tool | What it does |
|---|---|
git_log(path?, max_results?, since?, ref?) |
Recent commits as {commits: [{hash, short_hash, author, email, date, subject, body}, ...], count}. path scopes to one file/dir; max_results defaults to 20; since is a git date string (“2 weeks ago”, “2026-01-01”); ref selects a branch/tag (default HEAD). |
git_show(ref, path?, max_diff_bytes?) |
Inspect a commit ({hash, author, email, date, subject, body, diff, diff_truncated, diff_total_bytes}) — or, with path, return the file's content at that commit ({ref, path, content, truncated, total_bytes}). |
git_diff(base?, head?, path?, staged?, max_bytes?) |
Diff output. No base/head: working tree vs index (or vs HEAD if staged=true). With base only: working tree vs base. With both: base..head. path scopes. Returns {diff, truncated, total_bytes}. |
git_blame(file_path, line) |
Blame for a single 1-indexed line. Returns {file_path, line, hash, short_hash, author, author_email, author_time, summary, previous_hash, line_content}. |
Resources (@-mention from your chat)
In addition to tools (called by the LLM) and prompts (picked from a slash
menu), AI Bridge exposes editor state as MCP resources under the
aibridge:// scheme. Clients that support resource @-mentions (Claude
Desktop, Cursor, and others — Claude Code does not yet) let you inline these
into a chat message; the client substitutes the content where the @mention
sat, so the LLM sees one composite message.
| Picker label | URI on the wire | What you get |
|---|---|---|
selection |
aibridge://selection |
The active view's selection, with file path + line range as a header and a syntax-fenced body. |
You don't type the URI. Most clients render an @-picker (Claude Desktop /
Cursor: hit @ or click an attach icon, search by label) and the URI
travels invisibly. The user-typed message ends up looking roughly like:
Evaluate @selection and tell me how to fix the bug causing an infinite loop.
The exact UX (inline chip vs. attachment) varies by client; the picker label is the only thing the server controls.
The aibridge:// URI prefix matches the package's short name and avoids
overlap with anything Sublime HQ may ship under their own namespace.
How it works
Sublime Text's Python API only runs inside Sublime Text. Earlier versions
of this project worked around that with an external Python process talking
to a thin TCP bridge in the plugin. That's gone. Now the MCP server itself
lives inside the plugin, served from a stdlib http.server thread:
LLM client ──MCP / Streamable-HTTP──▶ 127.0.0.1:8765/mcp
│
▼
AI Bridge plugin
(mcp_lite + tool dispatch)
│
▼
sublime / sublime_plugin APIs
There is no mcp Python package dependency. The protocol layer
(mcp_lite/) is a hand-written ~400-line stdlib-only implementation of the
MCP wire format — JSON-RPC 2.0 over Streamable-HTTP — sized for exactly the
tools this plugin exposes. This sidesteps pydantic_core, cryptography,
and other binary-wheel deps that don't play well with Sublime Text's
embedded Python.
The “active project” for any given call is whatever
sublime.active_window() returns — that's the ST window you most recently
focused, even when ST isn't the foreground app.
Requirements
- Sublime Text 4 (build 4081 or newer). Plugin runs under ST's bundled Python 3.8.
- An MCP-capable client (Claude Code, Claude Desktop, LM Studio, etc.)
- No
pipinstalls. No external Python. Stdlib only.
Installation
In Sublime Text: Preferences → Browse Packages.... This opens
%APPDATA%\Sublime Text\Packages\ on Windows.[1]
Create a folder called AI Bridge inside it and copy the entire
contents of this repo into it. Final layout:
Packages/
└── AI Bridge/
├── .python-version
├── AIBridge.py
├── AI Bridge.sublime-settings
├── Main.sublime-menu
├── mcp_lite/
│ ├── __init__.py
│ ├── jsonrpc.py
│ ├── schema.py
│ ├── server.py
│ └── transport_http.py
└── tools/
├── __init__.py
└── sublime_tools.py
Watch out for
.python-version. It's a hidden file on macOS/Linux and easy to drop during a copy. Without it, ST loads the plugin under Python 3.3 and you'll seeImportError: No module named 'typing'in the console.
The plugin auto-loads. Open ST's console (Ctrl+`); on a successful
load you'll see:
[AI Bridge] MCP HTTP transport listening on 127.0.0.1:8765/mcp
If port 8765 is busy, the plugin walks 8766–8774 looking for a free one.
The actual bound port is also written to <Cache>/AIBridge.port,
and the command palette entry AI Bridge: Show Port displays it.
[1]: On macOS: ~/Library/Application Support/Sublime Text/Packages/. On Linux: ~/.config/sublime-text/Packages/.
Settings
AI Bridge.sublime-settings:
{
"host": "127.0.0.1",
"port": 8765,
"allowed_origins": [],
"auth_token": ""
}
host stays on localhost — the server only listens on your own machine.
port is just the preferred port; the fallback walk above kicks in if
it's busy.
By default the server rejects any request whose Origin header isn't
localhost. allowed_origins adds extra browser origins (exact match,
including scheme and port) — use it when a web UI served from another host
drives the bridge from your browser. The request still goes to your own
127.0.0.1; only the Origin header differs.
auth_token, when set, requires every request to carry
Authorization: Bearer <token>. CORS only constrains browsers, so this is
the real access gate — set it whenever you widen allowed_origins.
Connecting MCP clients
Sublime Text must be running with the plugin loaded before any client will see tools.
Claude Code (CLI or desktop)
claude mcp add --transport http sublime-ai-bridge http://127.0.0.1:8765/mcp
Or edit %USERPROFILE%\.claude.json directly:
{
"mcpServers": {
"sublime-ai-bridge": {
"type": "http",
"url": "http://127.0.0.1:8765/mcp"
}
}
}
Restart Claude Code (close and relaunch — including any background tray
process). Open a new conversation; the mcp__sublime-ai-bridge__* tools should
appear.
Claude Desktop
Edit %APPDATA%\Claude\claude_desktop_config.json. Some Claude Desktop
builds support native HTTP MCP servers; others only support stdio. The
stdio bridge form below works on every version (requires
Node.js installed):
{
"mcpServers": {
"sublime-ai-bridge": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://127.0.0.1:8765/mcp"]
}
}
}
mcp-remote is a tiny shim that turns the HTTP MCP server into a stdio
one. Fully quit Claude Desktop (including system tray) and relaunch.
If your build supports native HTTP, this also works:
{
"mcpServers": {
"sublime-ai-bridge": {
"type": "http",
"url": "http://127.0.0.1:8765/mcp"
}
}
}
LM Studio
Edit %USERPROFILE%\.lmstudio\mcp.json (or use Program → Install → Edit
mcp.json in the app):
{
"mcpServers": {
"sublime-ai-bridge": {
"url": "http://127.0.0.1:8765/mcp"
}
}
}
Toggle the server on in the Program panel.
Smoke test
In a new conversation with any connected client, ask:
Use sublime-ai-bridge to list folders in the project and find the function definition of [some function name in your project].
If you get the folder list and a definition location back, the chain is alive.
Coordinate convention
Every tool reads and writes row/column values as 1-indexed, matching
what Sublime's status bar displays (“Line 12, Column 4”). This is normalized
at the boundary — Sublime's view.rowcol() is internally 0-indexed, but
you never see that.
Practical implication: a row from find_function_in_project's output can
be passed directly into get_function_content(..., row=N) or
set_function_content(..., row=N) to disambiguate when the same name
appears multiple times in one file.
Edit semantics
set_function_content, set_file_content, and apply_edits_to_file
follow these rules:
- One call = one undo entry. Whether you replace one character or
fifty lines,
Ctrl+Zin Sublime reverts the whole call. - **
save=False(default)** leaves the buffer dirty so you can review andCtrl+S(orCtrl+Z) yourself. - If the file isn't already open, the plugin loads it as a transient
view, applies the edit, forces a save regardless of the
save=flag, and closes the view. Otherwise the edit would be lost when the transient view closes. - Read/write asymmetry.
get_file_contentreads from disk;set_file_contentand the function-edit tools go through ST's view layer so they integrate with undo and respect any unsaved buffer state. If you need to grep the unsaved buffer text, usesearch_text_in_open_files. - First match wins when a name is ambiguous in a single file. Pass
row=to target a specific occurrence.
get_function_content uses Sublime's syntax scopes (meta.function,
meta.method, meta.class, etc.) to find the body, falling back to
bracket matching that skips strings and comments. Works reliably for PHP,
JS/TS, Python, Ruby, Go, Rust, C/C++, Java. Less common syntax packages
may not define those scopes — in which case the bracket fallback handles
any {}-delimited language.
Known limits
- Symbol lookups only see what Sublime has indexed. Files outside
project folders, or files whose syntax has no symbol indexer, won't
appear in
find_function_in_project/find_function_usages_in_project. This is a deliberate tradeoff — the bridge does not force re-indexing. - Content search uses Python's
remodule, not Sublime's Boost regex. Most patterns are identical, but advanced features (some lookbehinds, named groups) follow Python semantics. - **
search_text_in_projectis timeout-bounded** at 30s by default. Adversarial regex ((a+)+bstyle) and large unfiltered project trees both hit this wall. The catastrophic case is hard-killed via cooperative cancellation, ST stays responsive, and the next call works. If routine searches time out, your project'sfolder_exclude_patternslikely need to skipvendor,node_modules, framework code, etc. - No auth. The server listens on
127.0.0.1only and rejects non-localhostOriginheaders. Anything else that can run code on your machine can call it. Don't expose port 8765 to a network. - Edits in dirty buffers are written to the buffer, not disk. If you
have unsaved changes in ST and ask the LLM to edit the same file, the
LLM's edit layers on top of your unsaved work in a single undo entry —
Ctrl+Zreverts both at once.
Troubleshooting
ImportError: No module named 'typing' on plugin load.
Sublime Text 4 defaults plugins to Python 3.3 unless a .python-version
file with the literal string 3.8 exists at the package root. Make sure
that file is present in Packages/AI Bridge/.
MCP client doesn't show any sublime-ai-bridge tools.
1. Verify the plugin is loaded: open ST's console (Ctrl+`). On
startup you should see
[AI Bridge] MCP HTTP transport listening on 127.0.0.1:8765/mcp.
Any traceback there is your culprit.
2. Verify the server is bound: curl -i http://127.0.0.1:8765/mcp should
give an HTTP response (a 200 SSE stream for GET, since the server
serves an idle event stream on that endpoint).
3. Some clients only load MCP servers at startup — fully restart the
client (including system tray) after editing its config.
Port already in use on another tool's side.
The plugin walks 8765 → 8774 to find a free port. Use the command palette
AI Bridge: Show Port to see what got bound, and update your MCP
client's URL to match. The bound port is also written to
<Cache>/AIBridge.port.
**definition '...' not found from set_function_content.**
The symbol exists in the index but view.symbol_regions() for that file
doesn't list it under the exact name you passed, or scope detection
couldn't locate the body. Run list_functions_in_file(file_path) to see
exactly what names are recognized. As a fallback, use apply_edits_to_file
with explicit row/col regions.
Edit applied but on-disk file unchanged.
You called set_function_content (or sibling) with save=false (the
default) on a file that was already open in Sublime. Either save in ST
manually, call save_open_file, or pass save=true to the edit call.
License
MIT — see LICENSE.