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
.vsixfrom the latest release. -
Reload VS Code — the extension starts automatically on startup, listening on
http://127.0.0.1:9876. -
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, tool registration, deactivation
└─ src/config.ts — Settings (port, auth, TLS) from VS Code + env fallbacks
└─ src/mcp/
├─ server.ts — HTTP server: CORS, auth, TLS, SSE transport, JSON-RPC dispatch
├─ transport.ts — JSON-RPC 2.0 handler + MCP protocol lifecycle (initialize, tools/list, tools/call)
└─ tools/
├─ commands.ts — Execute any VS Code command, list commands, get code actions
├─ navigation.ts — Open files, line/column jump, select lines, explorer reveal, close editors
├─ workspace.ts — Read/write/create/delete files, glob search, workspace folders
├─ debug.ts — Start/stop debugging, breakpoints (add/remove/list), step/continue,
│ stack trace, variables, frame-scoped evaluate
├─ terminal.ts — Execute commands in integrated terminal, capture output (30s timeout)
└─ 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
Tools
| 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 |
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 |
start_debugging |
debug | Start a debug session from a launch config (launch.json or workspace file) |
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 — can read locals like pre, self, t |
execute_in_terminal |
terminal | Execute command in integrated terminal (30s output capture 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 |
Connecting from other AI tools
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 (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
Originheader. The server acceptshttp://127.0.0.1:<port>,http://localhost:<port>, andhttp://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
Windsurf / Continue.dev / Any MCP-compatible tool
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:
| Method | Setting |
|---|---|
| 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
{
"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
{
"mcpServers": {
"vscode-mcp": {
"type": "remote",
"url": "https://127.0.0.1:9876/mcp",
"headers": {
"Authorization": "Bearer <your-token>"
}
}
}
}
Cursor
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
Origin header 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.
Example: tool list (42 tools)
When connected, tools/list returns schemas for all tools. Key categories:
| Category | Tools |
|---|---|
| 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, write_file, create_file, delete_file, list_files, get_workspace_folders |
| 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 |
| 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 schemastools/call— invokes a tool by name with arguments
Port Retry
If the default port (9876) is busy, the server tries up to 5 consecutive ports (±1 each try). If all fail, the extension logs an error and deactivates.
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). ⚠️ If set without TLS, a warning is logged (cleartext risk) |
tlsCertPath |
"" |
TLS cert PEM path (enables HTTPS) |
tlsKeyPath |
"" |
TLS key PEM path (enables HTTPS) |
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) |
MCP_SERVER_MAX_RETRIES |
port retry count | 3 |
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— passfolderto target a specific workspace folder (defaults to the first) - The workspace file (
*.code-workspace)launchsection — available in multi-root workspaces opened via a.code-workspacefile
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 # 75 tests across 3 suites (server, transport, tools)
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: watchtask 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
workspaceFolderto any file tool (read_file,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.jsonor in the workspace file (*.code-workspace).start_debuggingchecks 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 moresuffix sourceMap: trueis enabled — breakpoints work in the debugger when developing the extension itself