This document explains the protocol and architecture behind Claude Code's IDE integrations, based on reverse-engineering the VS Code extension. Use this guide to build your own integrations or understand how the official ones work.
Claude Code extensions create WebSocket servers in your IDE that Claude connects to. They use a WebSocket variant of MCP (Model Context Protocol) that only Claude supports. The IDE writes a lock file with connection info, sets some environment variables, and Claude automatically connects when launched.
When you launch Claude Code from your IDE, here's what happens:
The extension starts a WebSocket server on a random port (10000-65535) that listens for connections from Claude.
The IDE writes a discovery file to ~/.claude/ide/[port].lock:
{
"pid": 12345, // IDE process ID
"workspaceFolders": ["/path/to/project"], // Open folders
"ideName": "VS Code", // or "Neovim", "IntelliJ", etc.
"transport": "ws" // WebSocket transport
}When launching Claude, the IDE sets:
CLAUDE_CODE_SSE_PORT: The WebSocket server portENABLE_IDE_INTEGRATION: Set to "true"
Claude reads the lock files, finds the matching port from the environment, and connects to the WebSocket server.
Communication uses WebSocket with JSON-RPC 2.0 messages:
{
"jsonrpc": "2.0",
"method": "method_name",
"params": {
/* parameters */
},
"id": "unique-id" // for requests that expect responses
}The protocol is based on MCP (Model Context Protocol) specification 2025-03-26, but uses WebSocket transport instead of stdio/HTTP.
These are notifications the IDE sends to keep Claude informed:
Sent whenever the user's selection changes:
{
"jsonrpc": "2.0",
"method": "selection_changed",
"params": {
"text": "selected text content",
"filePath": "/absolute/path/to/file.js",
"fileUrl": "file:///absolute/path/to/file.js",
"selection": {
"start": { "line": 10, "character": 5 },
"end": { "line": 15, "character": 20 },
"isEmpty": false
}
}
}When the user explicitly sends a selection as context:
{
"jsonrpc": "2.0",
"method": "at_mentioned",
"params": {
"filePath": "/path/to/file",
"lineStart": 10,
"lineEnd": 20
}
}According to the MCP spec, Claude should be able to call tools, but current implementations are mostly one-way (IDE → Claude).
{
"jsonrpc": "2.0",
"id": "request-123",
"method": "tools/call",
"params": {
"name": "openFile",
"arguments": {
"filePath": "/path/to/file.js"
}
}
}{
"jsonrpc": "2.0",
"id": "request-123",
"result": {
"content": [{ "type": "text", "text": "File opened successfully" }]
}
}The extensions register these tools that Claude can (theoretically) call:
-
openFile - Open a file and optionally select text
{ "filePath": "/path/to/file.js", "startText": "function hello", // Find and select from this text "endText": "}" // To this text } -
openDiff - Show a diff and wait for user action (blocking!)
{ "old_file_path": "/path/to/original.js", "new_file_path": "/path/to/modified.js", "new_file_contents": "// Modified content...", "tab_name": "Proposed changes" }Returns
FILE_SAVEDorDIFF_REJECTEDbased on user action. -
getCurrentSelection - Get the current text selection
-
getOpenEditors - List all open files
-
getWorkspaceFolders - Get project folders
-
getDiagnostics - Get errors/warnings from the IDE
-
saveDocument - Save a file
-
close_tab - Close a tab by name (note the inconsistent naming!)
- Most tools follow camelCase naming except
close_tab(uses snake_case) - The
openDifftool is unique - it's blocking and waits for user interaction - Tools return MCP-formatted responses with content arrays
- There's also
executeCodefor Jupyter notebooks in the VS Code extension
Here's the minimum viable implementation:
-- Listen on localhost only (important!)
local server = create_websocket_server("127.0.0.1", random_port)-- ~/.claude/ide/[port].lock
local lock_data = {
pid = vim.fn.getpid(),
workspaceFolders = { vim.fn.getcwd() },
ideName = "YourEditor",
transport = "ws"
}
write_json(lock_path, lock_data)export CLAUDE_CODE_SSE_PORT=12345
export ENABLE_IDE_INTEGRATION=true
claude # Claude will now connect!-- Send selection updates
send_message({
jsonrpc = "2.0",
method = "selection_changed",
params = { ... }
})
-- Implement tools (if needed)
register_tool("openFile", function(params)
-- Open file logic
return { content = {{ type = "text", text = "Done" }} }
end)Always bind to localhost (127.0.0.1) only! This ensures the WebSocket server is not exposed to the network.
With this protocol knowledge, you can:
- Build integrations for any editor
- Create agents that connect to existing IDE extensions
- Extend the protocol with custom tools
- Build bridges between different AI assistants and IDEs
The WebSocket MCP variant is currently Claude-specific, but the concepts could be adapted for other AI coding assistants.
- MCP Specification
- Claude Code Neovim Implementation
- Official VS Code Extension (minified source)