VS Code MCP Server

Let AI agents read, write, debug, and execute commands in VS Code — just like a
human developer. This MCP (Model Context Protocol)
server exposes 40+ VS Code tools (debugger, terminal, LSP, file ops, commands)
over SSE, compatible with opencode, Claude, Cursor, and any MCP client.
Quick Start
Install the extension from the
VS Code Marketplace
or install a .vsix from the latest release.
Reload VS Code — the extension starts automatically on startup. The first window becomes the
leader and listens on http://127.0.0.1:9876; additional windows join as workers over an
IPC socket at <tmpdir>/vscode-mcp/ipc.sock (POSIX; named pipe on Windows) and share the same
port (the MCP client targets a window via a workspace argument). The socket path is configurable
via the vscode-mcp-server.ipcPath setting or VSCODE_MCP_IPC_PATH env var.
Configure your AI tool (e.g., opencode) to connect via SSE:
{
"vscode-mcp": {
"type": "remote",
"url": "http://127.0.0.1:9876/mcp"
}
}
Verify the server is running:
curl -s -X POST http://127.0.0.1:9876/mcp \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Architecture
VS Code Extension (onStartupFinished)
└─ src/extension.ts — Lifecycle: activation, cluster election, re-election,
tool registration, window-state publishing, deactivation
└─ src/mcp/
├─ executor.ts — Shared tool executor: registration, dispatch, concurrency cap, timeouts
├─ server.ts — HTTP server: CORS, auth, TLS, SSE transport, JSON-RPC dispatch
├─ transport.ts — JSON-RPC 2.0 handler + MCP protocol lifecycle
├─ cluster/
│ ├─ election.ts — Port probing (/health + IPC liveness) → valid | free | foreign | zombie
│ ├─ constants.ts — Port/IPC/heartbeat/timeout constants + MSG protocol types
│ ├─ leader.ts — LeaderCoordinator: owns the HTTP port, routes tools/call
│ │ to workers, serves list_workspaces
│ ├─ worker.ts — WorkerCoordinator: joins leader via IPC, heartbeat failover + re-election
│ ├─ bootstrap.ts — Bounded retry loop: promote (leader) or join (worker), with backoff
│ ├─ protocol.ts — Length-prefixed JSON framing + WindowState for the IPC pipe
│ └─ ipc.ts — Unix-socket helpers (stale-socket recovery, liveness checks)
└─ tools/
├─ commands.ts — Execute/catalog VS Code commands, get code actions
├─ navigation.ts — Open files, jump to line/col, select, reveal, close editors
├─ workspace.ts — Read/write/delete files, glob search, folder CRUD
├─ debug.ts — Debug lifecycle, breakpoints, stepping, stack/vars/evaluate
├─ terminal.ts — Run commands in integrated terminal, capture output (30s)
├─ search.ts — Full-text content search across workspace folders (grep)
└─ lsp.ts — Diagnostics (200-line cap), hover, references, definitions,
symbols, completions, code actions, call hierarchy, rename
└─ src/utils/
├─ types.ts — MCP method constants, type definitions
└─ path.ts — Workspace-root-aware path resolution
| Tool |
Module |
Description |
execute_command |
commands |
Execute any VS Code command by ID |
list_commands |
commands |
List all available VS Code commands (optionally internal) |
get_code_actions |
commands |
Get available refactors/quick fixes at a line |
open_file |
navigation |
Open a file in the editor |
open_file_at_line |
navigation |
Open a file at a specific line |
open_file_at_position |
navigation |
Open a file at a specific line and column |
select_lines |
navigation |
Select lines in the active editor |
reveal_in_explorer |
navigation |
Reveal file in the sidebar |
focus_editor |
navigation |
Focus the editor group |
close_editor |
navigation |
Close the active editor |
close_all_editors |
navigation |
Close all editors |
list_files |
workspace |
List files by glob pattern |
read_file |
workspace |
Read file content |
read_files |
workspace |
Read multiple files in one call (batch, per-file errors inline) |
write_file |
workspace |
Write content to a file (creates or overwrites) |
create_file |
workspace |
Create a new empty file |
delete_file |
workspace |
Delete a file or directory (recursive, use trash) |
get_workspace_folders |
workspace |
List workspace roots |
list_workspaces |
cluster |
List windows (leader + workers): id, name, folders, role, state |
add_workspace_folder |
workspace |
Add a folder to the workspace (multi-root) |
update_workspace_folder |
workspace |
Rename/change a workspace folder's path (multi-root) |
remove_workspace_folder |
workspace |
Remove a folder from the workspace (multi-root) |
search_files |
search |
Grep file contents across the workspace (regex, case-insensitive) |
list_logs |
logs |
List VS Code log sessions and files |
read_log |
logs |
Tail a VS Code log file (with optional grep filter) |
start_debugging |
debug |
Start a debug session from a folder/workspace launch config |
stop_debugging |
debug |
Stop the active debug session |
step_over |
debug |
Step over current line |
step_into |
debug |
Step into function |
step_out |
debug |
Step out of function |
continue |
debug |
Continue execution |
add_breakpoint |
debug |
Add breakpoint (supports condition, hitCondition) |
remove_breakpoint |
debug |
Remove a breakpoint |
list_breakpoints |
debug |
List all breakpoints |
get_debug_variables |
debug |
Get frame-local variables from paused session |
get_stack_trace |
debug |
Get call stack frames |
evaluate_in_debug_console |
debug |
Frame-scoped evaluate — reads paused-session locals |
execute_in_terminal |
terminal |
Execute command in integrated terminal (30s output timeout) |
get_terminal_output |
terminal |
Get terminal output buffer |
find_references |
lsp |
Find all references to symbol at cursor |
go_to_definition |
lsp |
Navigate to symbol definition |
go_to_type_definition |
lsp |
Navigate to type definition |
go_to_implementation |
lsp |
Navigate to symbol implementation |
get_hover |
lsp |
Get hover info at cursor |
get_diagnostics |
lsp |
Get file diagnostics (capped at 200 lines) |
get_document_symbols |
lsp |
Get symbols in active document |
get_workspace_symbols |
lsp |
Search workspace symbols |
get_call_hierarchy |
lsp |
Get incoming and outgoing call hierarchy |
rename_symbol |
lsp |
Rename symbol across workspace |
get_completions |
lsp |
Get completion items at cursor |
Cluster mode
Every VS Code window runs one cluster member. Exactly one window (the
leader) owns the HTTP port; every other window (a worker) connects to
it over a local IPC pipe. Clients connect to the single leader port and target
a specific window via the workspace argument on tools/call / tools/list
(see below).
list_workspaces
list_workspaces returns one row per window. Each row carries stable identity
plus live editor state:
[
{
"id": "/path/to/folder:12345",
"instanceId": "9f7b…uuid…",
"instanceName": "My Project",
"displayName": "My Project",
"folders": ["/path/to/folder"],
"role": "leader",
"state": {
"activeFile": "/path/to/folder/src/main.ts",
"openEditors": ["/path/to/folder/src/main.ts", "/path/to/folder/src/util.ts"]
}
}
]
role — "leader" (owns the port) or "worker" (joined over IPC).
state.activeFile — absolute path of the active editor, when one is open
(omitted otherwise).
state.openEditors — absolute paths of all open editor tabs; always present.
State is refreshed automatically as editors open, close, or change focus
(debounced, pushed over IPC for workers), so two windows on the same folder
are distinguishable by which files they have open.
Targeting a window
Pass workspace on tools/call / tools/list to route to a specific window.
Accepted values:
- a row
id from list_workspaces
- an
instanceId (stable per-window UUID)
- a folder path contained in that window's
folders
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "open_file",
"arguments": { "path": "src/main.ts" },
"workspace": "/path/to/folder"
}
}
Without workspace, calls target the leader window.
opencode
Add to your opencode.global.jsonc or opencode.json:
{
"mcpServers": {
"vscode-mcp": {
"type": "remote",
"url": "http://127.0.0.1:9876/mcp"
}
}
}
Claude Desktop / Claude Code
Claude Code uses the [Streamable HTTP transport](https://github.com/nabheet/vscode-mcp-server/blob/HEAD/
https:/spec.modelcontextprotocol.io/specification/2025-03-26/basic/transports/)
(MCP 2025-03-26). The server supports this via direct POST /mcp — no SSE
preamble needed.
Add to claude_desktop_config.json:
{
"mcpServers": {
"vscode-mcp": {
"type": "remote",
"url": "http://127.0.0.1:9876/mcp"
}
}
}
Troubleshooting: If Claude Code fails to connect, check that it's not
sending an incompatible Origin header. The server accepts
http://127.0.0.1:<port>, http://localhost:<port>, and
http://0.0.0.0:<port>. See
Origin header troubleshooting below.
Or via stdio if you prefer a managed subprocess:
{
"mcpServers": {
"vscode-mcp": {
"command": "node",
"args": ["path/to/vscode-mcp-server/out/cli.js"]
}
}
}
Cursor
In Cursor Settings → Features → MCP Servers → Add new MCP server:
Name: vscode-mcp
Type: remote
URL: http://127.0.0.1:9876/mcp
Add a type: "remote" MCP server pointing to:
http://127.0.0.1:9876/mcp
The server uses SSE transport (the standard MCP HTTP transport). If the tool
only supports stdio, you can use an SSE-to-stdio bridge like mcp-remote or
write a thin wrapper.
Connecting programmatically
# Example: using the MCP Python SDK
from mcp import ClientSession
from mcp.client.sse import sse_client
async with sse_client("http://127.0.0.1:9876/mcp") as transport:
async with ClientSession(transport) as session:
result = await session.list_tools()
for tool in result.tools:
print(f"{tool.name}: {tool.description}")
// Example: using the MCP TypeScript SDK
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
const transport = new SSEClientTransport(new URL("http://127.0.0.1:9876/mcp"));
const client = new Client({ name: "my-agent", version: "1.0.0" });
await client.connect(transport);
const tools = await client.listTools();
With TLS and authentication
Enable TLS and/or auth via VS Code settings or environment variables, then
update your client URL and headers accordingly.
Server-side setup:
- VS Code settings —
vscode-mcp-server.tlsCertPath,
vscode-mcp-server.tlsKeyPath, vscode-mcp-server.authToken
- Env vars —
MCP_TLS_CERT_PATH, MCP_TLS_KEY_PATH, MCP_AUTH_TOKEN
opencode (TLS)
{
"mcpServers": {
"vscode-mcp": {
"type": "remote",
"url": "https://127.0.0.1:9876/mcp", // https, not http
"headers": {
"Authorization": "Bearer <your-token>"
}
}
}
}
Claude Desktop / Claude Code (TLS)
{
"mcpServers": {
"vscode-mcp": {
"type": "remote",
"url": "https://127.0.0.1:9876/mcp",
"headers": {
"Authorization": "Bearer <your-token>"
}
}
}
}
Cursor (TLS)
In Cursor Settings → Features → MCP Servers:
Name: vscode-mcp
Type: remote
URL: https://127.0.0.1:9876/mcp
Headers: { "Authorization": "Bearer <your-token>" }
Programmatic (Python with TLS + auth)
from mcp import ClientSession
from mcp.client.sse import sse_client
async with sse_client(
"https://127.0.0.1:9876/mcp",
headers={"Authorization": "Bearer <your-token>"},
) as transport:
async with ClientSession(transport) as session:
result = await session.list_tools()
Programmatic (TypeScript with TLS + auth)
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
const transport = new SSEClientTransport(
new URL("https://127.0.0.1:9876/mcp"),
{ headers: { Authorization: "Bearer <your-token>" } }
);
const client = new Client({ name: "my-agent", version: "1.0.0" });
await client.connect(transport);
curl (for testing)
# With TLS
curl -sk https://127.0.0.1:9876/mcp \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <your-token>' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
# With TLS + auth, direct POST
curl -sk -X POST https://127.0.0.1:9876/mcp \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <your-token>' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
⚠️ Security note: When auth token is set without TLS, the server logs a
warning. Bearer tokens over plain HTTP can be intercepted on the local
network. Use TLS for any non-loopback access.
Troubleshooting
Some MCP clients (including recent Claude Code versions) send an Origin HTTP
header when connecting via Streamable HTTP (POST /mcp). If the origin doesn't
match an allowed loopback address, the server rejects the request with
403 Forbidden.
Allowed origins (configurable via the server's host setting):
| Origin |
Default? |
http://127.0.0.1:<port> |
✅ Always accepted |
http://localhost:<port> |
✅ Always accepted |
http://0.0.0.0:<port> |
✅ Always accepted |
http://<configured-host>:<port> |
✅ Only if host differs from above |
No Origin header (non-browser client) |
✅ Always accepted |
| Any other origin |
❌ Rejected |
To diagnose: check the server logs for 403 responses when your client tries to connect:
# Verify the server is running
curl -s -X POST http://127.0.0.1:9876/mcp \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
If curl works but your client doesn't, the client is likely sending an
Origin header that doesn't match. Check your client's MCP transport
configuration — some allow setting custom headers.
SSE transport deprecation
The old MCP SSE transport (GET /mcp → SSE stream → endpoint event →
POST /mcp/session/:id/message) is deprecated as of the MCP 2025-03-26
specification. The new standard is Streamable HTTP transport (POST /mcp
with direct JSON-RPC response).
This server supports both transports transparently — no configuration
change needed. Just use POST /mcp as the endpoint and the server handles
everything synchronously.
When connected, tools/list returns schemas for all tools. Key categories:
- Editor —
open_file, open_file_at_line, open_file_at_position,
select_lines, reveal_in_explorer, focus_editor, close_editor,
close_all_editors
- Workspace —
read_file, read_files, write_file, create_file,
delete_file, list_files, get_workspace_folders,
add_workspace_folder, update_workspace_folder, remove_workspace_folder
- Search —
search_files
- Debug —
start_debugging, stop_debugging, step_over, step_into,
step_out, continue, add_breakpoint, remove_breakpoint,
list_breakpoints, get_debug_variables, get_stack_trace,
evaluate_in_debug_console
- Terminal —
execute_in_terminal, get_terminal_output
- Logs —
list_logs, read_log
- LSP —
find_references, go_to_definition, go_to_type_definition,
go_to_implementation, get_hover, get_diagnostics,
get_document_symbols, get_workspace_symbols, get_call_hierarchy,
rename_symbol, get_completions, get_code_actions
- Commands —
execute_command, list_commands
MCP Protocol
Transport
Supports three transport modes:
1. Streamable HTTP (recommended, MCP 2025-03-26) — POST /mcp with
JSON-RPC body. Synchronous request/response. This is the new standard
transport used by Claude Code and recent MCP SDK clients. No session setup
or SSE handshake needed.
2. SSE (legacy) — GET /mcp opens an SSE stream, server sends an
endpoint event with a session-specific POST URL. Client sends JSON-RPC
messages to POST /mcp/session/:id/message, responses arrive via SSE
message events. Deprecated in favor of Streamable HTTP but still supported.
3. Direct POST (backward compat) — POST /mcp with JSON-RPC body.
Synchronous request/response. This is identical to Streamable HTTP at the
wire level.
Lifecycle
Full MCP protocol lifecycle implemented:
- Client sends
initialize — server responds with protocol version
(2024-11-05), capabilities (tools), and server info
- Client sends
notifications/initialized — acknowledges readiness (no response expected)
tools/list — returns all tool definitions with JSON schemas
tools/call — invokes a tool by name with arguments
Port Retry
If the default port (9876) is busy, the leader scans up to 5 consecutive ports
(9876..9880, controlled by MCP_SERVER_MAX_RETRIES). If a port is occupied by
a non-MCP process, the next port is tried. The election loop retries the whole
scan up to 8 times with exponential backoff; workers re-join the elected leader
over IPC rather than taking their own port.
Configuration
All settings under vscode-mcp-server.*:
| Setting |
Default |
Description |
port |
9876 |
HTTP server port (auto-retries if busy) |
authToken |
"" |
Bearer token (empty = no auth). Warns if set without TLS |
tlsCertPath |
"" |
TLS cert PEM path (enables HTTPS) |
tlsKeyPath |
"" |
TLS key PEM path (enables HTTPS) |
ipcPath |
"" |
Cluster IPC socket: <tmpdir>/vscode-mcp/ipc.sock POSIX, pipe Windows |
Settings fall back to environment variables:
| Env var |
Overrides |
Default |
MCP_PORT |
port |
9876 |
MCP_AUTH_TOKEN |
authToken |
(none) |
MCP_TLS_CERT_PATH |
tlsCertPath |
(none) |
MCP_TLS_KEY_PATH |
tlsKeyPath |
(none) |
VSCODE_MCP_IPC_PATH |
ipcPath |
(none — <tmpdir>/vscode-mcp/ipc.sock POSIX) |
MCP_SERVER_MAX_RETRIES |
ports scanned per election (default 5, 9876–9880) |
5 |
VS Code settings take priority over env vars.
Security
- CORS restricted to loopback origin (
Access-Control-Allow-Origin: http://127.0.0.1:<port>)
- Bearer token auth uses timing-safe comparison
- Payload limit: 1 MB
- TLS supported but not required (loopback-only by default)
- Warning logged when auth token is set without TLS
Debug Tips
Frame-Scoped Evaluation
evaluate_in_debug_console automatically resolves the top stack frame's
frameId and passes it to the DAP evaluate request. This means you can read
local variables directly:
evaluate_in_debug_console("pre") → 69.75
evaluate_in_debug_console("self") → Order(order_id='ORD-001', ...)
Without a paused debug session, it falls back to global-scope evaluation.
Debug Workflow
- Open target file:
open_file("src/main.py")
- Set breakpoints:
add_breakpoint("src/main.py", 42)
- Start debugging:
start_debugging("Launch Config Name")
- Step through:
step_over(), step_into(), step_out()
- Inspect:
get_stack_trace(), get_debug_variables(), evaluate_in_debug_console("my_var")
- Continue:
continue()
- Stop:
stop_debugging()
Launch Config Locations
start_debugging finds a config by name in two places, preferring folder-level configs:
- A folder's
.vscode/launch.json — pass folder to target a specific
workspace folder (defaults to the first)
- The workspace file (
*.code-workspace) launch section — available in
multi-root workspaces opened via a .code-workspace file
For workspace-file configs, the extension reads the config from the workspace
file and starts it directly, which also works around a VS Code quirk where
name-based lookup with an undefined folder fails.
Development
npm install
npm run compile # Build TypeScript → out/
npm run watch # Watch mode
npm test # Unit tests (server, transport, tools, cluster, …)
npm run test:e2e # End-to-end: launches real VS Code windows and verifies cluster routing
Debug the extension itself
- Press F5 in VS Code (uses
.vscode/launch.json "Run Extension" config)
- A new Extension Development Host window opens
- The MCP server starts automatically on port 9876
- Set breakpoints in
src/ to debug tool handlers
- The
npm: watch task auto-compiles on save
Package for distribution
npx @vscode/vsce package
# Produces vscode-mcp-server-*.vsix
Releases
Publishing is fully automated from GitHub Actions — no local login needed.
Stable — every merge to main auto-publishes the next patch to the
VS Code Marketplace:
vsce publish patch bumps package.json, publishes, tags vX.Y.Z, creates a
GitHub release (VSIX attached), then syncs the bumped version back to main
via an auto-PR (chore: bump version to vX.Y.Z; a job-level guard matching
the sync-back commit message prevents it from re-triggering publishing).
Pre-release — every push to an open PR publishes a unique pre-release
(0.9.<workflow-run>) to the Marketplace pre-release channel. This dedicated
line is always above the stable 0.3.x line, so it never collides with
stable releases; the published version is commented on the PR.
See RELEASE.md for details.
Notes
- Workspace file operations target the first workspace root by default. In
multi-root workspaces, pass
workspaceFolder to any file tool (read_file,
read_files, write_file, create_file, delete_file, list_files,
open_file, open_file_at_line, open_file_at_position,
reveal_in_explorer, add_breakpoint, remove_breakpoint) to operate on a
specific folder
- Debug tools require an active debug configuration — either in a folder's
.vscode/launch.json or in the workspace file (*.code-workspace).
start_debugging checks both, preferring folder-level configs
- Terminal tools create integrated terminals in VS Code; output capture has a
30-second timeout to prevent resource leaks
- LSP tools query the active language server; diagnostics are capped at 200
lines with
... and N more suffix
sourceMap: true is enabled — breakpoints work in the debugger when developing the extension itself