ctrl+shift+p filters: :st2 :st3 :win :osx :linux
Browse

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 kind field 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 pip installs. 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 see ImportError: 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+Z in Sublime reverts the whole call.
  • **save=False (default)** leaves the buffer dirty so you can review and Ctrl+S (or Ctrl+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_content reads from disk; set_file_content and 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, use search_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 re module, not Sublime's Boost regex. Most patterns are identical, but advanced features (some lookbehinds, named groups) follow Python semantics.
  • **search_text_in_project is timeout-bounded** at 30s by default. Adversarial regex ((a+)+b style) 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's folder_exclude_patterns likely need to skip vendor, node_modules, framework code, etc.
  • No auth. The server listens on 127.0.0.1 only and rejects non-localhost Origin headers. 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+Z reverts 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.