Reverse-engineered and validated by implementing a compatible extension for a different IDE. This document describes the protocol as observed — it is not an official spec.
The /ide protocol enables GitHub Copilot CLI
to communicate with a running IDE instance. When a user runs /ide in Copilot CLI,
the CLI discovers connected IDEs, displays the user's current editor context
(file, selection, diagnostics), and can propose file edits via a diff view.
The protocol has three layers:
| Layer | Purpose | Mechanism |
|---|---|---|
| Discovery | CLI finds running IDE instances | Lock files in ~/.copilot/ide/ |
| Transport | CLI sends requests / receives responses | Streamable HTTP over a named pipe / socket |
| Application | Tool calls and push notifications | MCP (Model Context Protocol) with JSON-RPC 2.0 |
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Copilot CLI │◄───MCP──────►│ MCP Server │◄────────────►│ IDE │
│ (/ide mode) │ Streamable │ (pipe/socket │ (internal) │ Extension │
│ │ HTTP │ listener) │ │ │
└──────────────┘ └──────────────┘ └──────────────┘
In VS Code captures, an MCP server listens on the named pipe / socket endpoint, accepts Streamable HTTP, and exposes the MCP tools documented below.
~/.copilot/ide/{uuid}.lock
Each running IDE instance writes a JSON lock file to this directory. The UUID is a random identifier generated at connection startup. Copilot CLI scans this directory to discover available IDEs.
| Field | Type | Description |
|---|---|---|
socketPath |
string | Full path to the named pipe or Unix domain socket the MCP server listens on. On Windows: \\.\pipe\{name}. On Unix: a filesystem path. |
scheme |
string | Transport scheme. Always "pipe". |
headers |
object | HTTP headers the CLI includes on every request. Contains Authorization: Nonce {guid} where the GUID is generated per session. |
pid |
number | OS process ID of the IDE or MCP server process. Used for stale lock file cleanup. |
ideName |
string | Human-readable IDE name (e.g. "Visual Studio Code", "JetBrains IntelliJ", "Neovim"). The CLI uses this value to select the display color for file paths and IDE labels in the terminal — see IDE Display Colors. |
timestamp |
number | Unix timestamp in milliseconds (UTC) when the lock file was written. |
workspaceFolders |
string[] | Absolute paths of open workspace / project folders. |
isTrusted |
boolean | Whether the workspace is trusted by the IDE. |
- Created when a workspace / project opens and the MCP server is ready to accept connections.
- Deleted when the workspace / project closes or the IDE shuts down.
In VS Code captures, socketPath uses: mcp-{guid}.sock
The .sock suffix is a naming convention. On Windows the actual transport is a
named pipe (\\.\pipe\mcp-{guid}.sock); on Unix it would be a domain socket.
The CLI uses the ideName field to colorize file paths and IDE labels in the
terminal. The color is determined by matching the ideName against known IDE
names:
ideName |
Terminal Color | Notes |
|---|---|---|
"Visual Studio Code" |
Blue | Matches VS Code's icon color |
"Visual Studio Code - Insiders" |
Cyan | Matches VS Code Insiders' icon color |
| (anything else) | White (default) | Unrecognized IDE names get no color |
This is a client-side display concern. The CLI uses ideName to determine
terminal color.
Copilot CLI connects to the pipe / socket specified in socketPath and speaks
HTTP/1.1 directly over it. This is the
Streamable HTTP MCP transport.
Every HTTP request MUST include the Authorization header from the lock file:
Authorization: Nonce {guid}
Requests without a valid nonce receive 401 Unauthorized.
Required on all requests:
Authorization: Nonce {guid}— authentication token from lock file
Observed headers on POST requests (VS Code captures):
Content-Type: application/jsonContent-Length: {bytes}orTransfer-Encoding: chunked— the CLI typically uses chunked encoding; servers MUST accept both framing methodsMcp-Session-Id: {sessionId}— associates the request with the active MCP sessionmcp-protocol-version: 2025-11-25— MCP protocol version value observed in captures
Additional headers observed in VS Code captures:
X-Copilot-Session-Id— CLI's internal session UUIDX-Copilot-PID— CLI process IDX-Copilot-Parent-PID— Parent process ID
All operations target a single path: /mcp (or /).
| Method | Path | Purpose |
|---|---|---|
POST |
/mcp |
Send a JSON-RPC request (tool call, initialize, etc.) |
GET |
/mcp |
Open an SSE stream for server-to-client push notifications |
DELETE |
/mcp |
Terminate the MCP session |
POST /mcp HTTP/1.1
Authorization: Nonce abc123
Content-Type: application/json
Transfer-Encoding: chunked
Mcp-Session-Id: session-a1b2c3d4
mcp-protocol-version: 2025-11-25
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_selection","arguments":{},"_meta":{"progressToken":1}}}Response (with result): HTTP 200 with SSE body:
HTTP/1.1 200 OK
content-type: text/event-stream
mcp-session-id: session-a1b2c3d4
transfer-encoding: chunked
event: message
data: {"jsonrpc":"2.0","id":1,"result":{"content":[...]}}Response (notification, no result expected): HTTP 202 Accepted with
Content-Type: text/plain. This applies to fire-and-forget messages such as
notifications/initialized which carry no JSON-RPC id and expect no result.
In VS Code captures, these notifications return 202 Accepted with
Content-Type: text/plain.
Timeout: Most tool calls have a 30-second timeout. The open_diff tool is exempt
because it blocks until the user accepts or rejects the diff (with a 1-hour ultimate
fallback).
The CLI opens an SSE stream to receive push notifications:
GET /mcp HTTP/1.1
Authorization: Nonce abc123
Mcp-Session-Id: session-a1b2c3d4HTTP/1.1 200 OK
content-type: text/event-stream
cache-control: no-cache, no-transform
connection: keep-alive
mcp-session-id: session-a1b2c3d4
transfer-encoding: chunkedThe server sends chunked SSE events for push notifications (see §4). This connection is kept alive until the session ends or the pipe disconnects.
DELETE /mcp HTTP/1.1
Authorization: Nonce abc123
Mcp-Session-Id: session-a1b2c3d4The response body is empty.
Returns 200 OK and closes the connection.
The server generates a session ID on the first POST and returns it via the
Mcp-Session-Id response header. The client includes this header on subsequent
requests to associate them with the same MCP session. In VS Code captures, this
ID is treated as an opaque string and echoed back unchanged by the client.
The server exposes MCP tools that Copilot CLI invokes via tools/call JSON-RPC
requests. The server MUST advertise itself as:
{
"name": "vscode-copilot-cli",
"version": "0.0.1",
"title": "VS Code Copilot CLI"
}Observed in VS Code captures: the server advertises this exact
name, withversion: "0.0.1"andtitle: "VS Code Copilot CLI".
{
"tools": {
"listChanged": true
}
}The protocol defines 6 tools. The names and schemas must match exactly for CLI compatibility.
| Tool | Description |
|---|---|
get_vscode_info |
Get IDE instance information |
get_selection |
Get the current text selection |
get_diagnostics |
Get language diagnostics (errors, warnings) |
open_diff |
Open a diff view with proposed changes |
close_diff |
Close a diff tab |
update_session_name |
Set the CLI session display name |
All tools declare execution.taskSupport: "forbidden" — they do not support MCP task-based
execution. This property is nested inside an execution wrapper on each tool definition,
not as a top-level taskSupport or under annotations:
{
"name": "get_selection",
"description": "...",
"inputSchema": { ... },
"execution": {
"taskSupport": "forbidden"
}
}Tool calls use simple request/response semantics (except open_diff
which is long-running but still uses request/response).
Note: No
annotationsproperty has been observed on any tool in wire captures. Theexecutionwrapper is the actual mechanism for declaring task support behavior.
Schema variations: VS Code's tool input schemas include
"additionalProperties": falseand"$schema": "http://json-schema.org/draft-07/schema#"on tools that have parameters.
Tool calls include a _meta object with metadata for streaming and progress tracking:
{
"method": "tools/call",
"params": {
"name": "get_selection",
"arguments": {},
"_meta": { "progressToken": 1 }
},
"jsonrpc": "2.0",
"id": 1
}| Field | Description |
|---|---|
_meta.progressToken |
Incrementing integer for streaming results. Opaque to the server — handled by the MCP transport layer. |
Servers implementing synchronous tools (non-streaming) can safely ignore _meta.
Returns information about the IDE instance.
The tool is named
get_vscode_infoeven for non-VS Code IDEs — the CLI matches on this exact name.
Parameters: None
Response:
{
"version": "1.111.0",
"appName": "Visual Studio Code",
"appRoot": "c:\\Users\\user\\AppData\\Local\\Programs\\Microsoft VS Code\\...\\resources\\app",
"language": "en",
"machineId": "71b362fc28cdb055fbf64ad8c09dceb80aa215fb914fd4df2dfe4eb7d98e2d25",
"sessionId": "397a91c4-1301-46b1-a499-66d64890db421773085295590",
"uriScheme": "vscode",
"shell": "C:\\Program Files\\PowerShell\\7\\pwsh.exe"
}| Field | Type | Description |
|---|---|---|
version |
string | IDE version string |
appName |
string | Application display name (e.g. "Visual Studio Code") |
appRoot |
string | Path to the IDE's application root directory |
language |
string | UI language code (e.g. "en") |
machineId |
string | Stable machine identifier (hashed) |
sessionId |
string | IDE session identifier |
uriScheme |
string | URI scheme for the IDE (e.g. "vscode", "vscode-insiders") |
shell |
string | Path to the default shell |
Note: These are the fields observed in VS Code wire captures.
Returns the current text selection or cursor position in the active editor.
Parameters: None
Response:
{
"current": true,
"filePath": "/home/user/project/src/index.ts",
"fileUrl": "file:///home/user/project/src/index.ts",
"text": "selected text here",
"selection": {
"start": { "line": 10, "character": 4 },
"end": { "line": 10, "character": 22 },
"isEmpty": false
}
}| Field | Type | Description |
|---|---|---|
current |
boolean | true if from the active editor, false if cached/stale |
filePath |
string? | Absolute file path |
fileUrl |
string? | file:/// URI (see File URI Convention) |
text |
string? | Selected text content (empty string if cursor-only) |
selection |
SelectionRange? | Position coordinates |
SelectionRange: { start: Position, end: Position, isEmpty: boolean }
Position: { line: number, character: number } — both 0-based.
Null response: If no editor is active and no cached selection exists, the tool returns
nullcontent (i.e. the MCP resultcontent[0].textis"null"). Clients MUST handle this case gracefully — display a "no file open" state or equivalent. Observed in VS Code when all editor tabs are closed.
Returns language diagnostics (errors, warnings, information) from the IDE.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
uri |
string | No | File URI to filter by. If omitted, returns diagnostics for all files. |
Response: Array of file diagnostic groups:
[
{
"uri": "file:///home/user/project/src/index.ts",
"filePath": "/home/user/project/src/index.ts",
"diagnostics": [
{
"message": "Cannot find name 'foo'",
"severity": "error",
"range": {
"start": { "line": 5, "character": 10 },
"end": { "line": 5, "character": 13 }
},
"code": "2304"
}
]
}
]| Field | Type | Description |
|---|---|---|
uri |
string | File URI |
filePath |
string | Absolute file path (OS-native format) |
diagnostics[].message |
string | Diagnostic message text |
diagnostics[].severity |
string | "error", "warning", or "information" |
diagnostics[].range |
Range | Location in file (0-based line/character) |
diagnostics[].code |
string? | Diagnostic code (e.g. "2304", "E0001") |
Opens a diff view comparing the original file with proposed new content. Blocks until the user accepts, rejects, or closes the diff tab.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
original_file_path |
string | Yes | Path to the original file |
new_file_contents |
string | Yes | The proposed new file content |
tab_name |
string | Yes | Display name for the diff tab |
Response:
{
"success": true,
"result": "SAVED",
"trigger": "accepted_via_button",
"tab_name": "refactor-auth",
"message": "User accepted changes for auth.ts"
}| Field | Type | Description |
|---|---|---|
success |
boolean | Whether the diff opened without error |
result |
string | Resolution: "SAVED" (accepted) or "REJECTED" |
trigger |
string | What caused the resolution (see table below) |
tab_name |
string | The tab name passed in |
message |
string? | Human-readable status message |
Trigger values:
| Trigger | Meaning |
|---|---|
accepted_via_button |
User clicked Accept |
rejected_via_button |
User clicked Reject |
closed_via_tool |
Another open_diff or close_diff call replaced this diff |
Blocking behavior: The MCP server MUST skip its normal 30-second timeout for
open_diffcalls. The tool blocks the HTTP response until the user acts. Copilot CLI is expected to handle this long-running call.
Programmatically closes a diff tab opened by open_diff.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
tab_name |
string | Yes | Tab name matching the open_diff call |
Response:
{
"success": true,
"already_closed": false,
"tab_name": "refactor-auth",
"message": "Diff \"refactor-auth\" closed and changes rejected"
}| Field | Type | Description |
|---|---|---|
success |
boolean | Whether the operation completed |
already_closed |
boolean | true if no active diff with that tab name was found |
tab_name |
string | Echo of the tab name |
message |
string? | Status message |
Closing a diff via this tool signals REJECTED with trigger closed_via_tool to
any pending open_diff call.
Sets a display name for the current CLI session.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | The new session name |
Response:
{
"success": true
}This is a fire-and-forget tool. In VS Code captures, the server acknowledges the request with a success response.
The server pushes real-time notifications to connected SSE clients as JSON-RPC 2.0 notifications. There are 2 notification types, both recommended to be debounced at ~200ms.
Sent when the user changes the active file, moves the cursor, or changes the text selection.
{
"jsonrpc": "2.0",
"method": "selection_changed",
"params": {
"text": "",
"filePath": "/home/user/project/src/index.ts",
"fileUrl": "file:///home/user/project/src/index.ts",
"selection": {
"start": { "line": 10, "character": 4 },
"end": { "line": 10, "character": 4 },
"isEmpty": true
}
}
}| Field | Type | Description |
|---|---|---|
text |
string | Selected text (empty string if no selection) |
filePath |
string | Absolute file path |
fileUrl |
string | File URI (see File URI Convention) |
selection |
SelectionRange | Cursor/selection position (0-based) |
Debounce: ~200ms recommended. Only the most recent selection should be sent. Deduplication (suppressing sends when file + positions haven't changed) is recommended to avoid redundant notifications.
Sent when diagnostics (errors, warnings) change — typically after builds complete or documents are saved.
{
"jsonrpc": "2.0",
"method": "diagnostics_changed",
"params": {
"uris": [
{
"uri": "file:///home/user/project/src/index.ts",
"diagnostics": [
{
"range": {
"start": { "line": 5, "character": 10 },
"end": { "line": 5, "character": 13 }
},
"message": "Cannot find name 'foo'",
"severity": "error",
"code": "2304"
}
]
}
]
}
}| Field | Type | Description |
|---|---|---|
uris |
array | Array of { uri, diagnostics } objects |
uris[].uri |
string | File URI |
uris[].diagnostics |
array | Diagnostics for this file (same schema as get_diagnostics) |
Debounce: ~200ms recommended.
Virtual URIs: IDEs may optionally send diagnostics for virtual URIs with empty diagnostics
arrays [] to indicate the buffer exists but has no errors. Known virtual URI schemes:
| Scheme | Usage |
|---|---|
git:// |
Diff buffers (Git working tree comparisons) |
copilot-cli-readonly:/ |
Read-only buffers used by the CLI for proposed file content |
Clients should handle virtual URIs gracefully — they may appear in both push notifications and tool responses.
Push notifications are sent as chunked-encoded SSE events:
event: message
data: {"jsonrpc":"2.0","method":"selection_changed","params":{...}}
Each event is wrapped in HTTP chunked transfer encoding:
{hex-length}\r\n
event: message\ndata: {json}\n\n
\r\n
File paths exchanged between CLI and IDE follow the standard file:/// URI scheme:
| Platform | File Path | File URI |
|---|---|---|
| Windows | C:\Dev\file.ts |
file:///c%3A/Dev/file.ts |
| macOS / Linux | /home/user/file.ts |
file:///home/user/file.ts |
Key rules:
- On Windows, drive letter is lowercase (
c:notC:) - On Windows, colon is percent-encoded as
%3A - Backslashes are converted to forward slashes
- File paths in
filePathfields use the OS-native format (but lowercase drive letter on Windows for consistency)
Copilot CLI MCP Server IDE
│ │ │
│ scans ~/.copilot/ide/*.lock │ │
│ reads socketPath + headers │ │
│ │ │
│──POST /mcp (initialize)──────►│ │
│◄─200 SSE (server capabilities)│ │
│ │ │
│──POST /mcp (initialized)─────►│ │
│◄─202 Accepted │ │
│ │ │
│──GET /mcp (SSE stream)───────►│ │
│◄─200 (chunked SSE)────────────│ │
│ │ │
│──POST /mcp (tools/list)──────►│ │
│◄─200 SSE (tool definitions)───│ │
│ │ │
│ (connection established, SSE stream remains open) │
Copilot CLI MCP Server IDE
│ │ │
│──POST {"method":"tools/call", │ │
│ "params":{"name": │ │
│ "get_selection"}}──────────►│ │
│ │──get selection──────────►│
│ │◄─selection data──────────│
│◄─200 SSE {"result":...}───────│ │
Copilot CLI MCP Server IDE / User
│ │ │
│──POST {"method":"tools/call", │ │
│ "params":{"name": │ │
│ "open_diff",...}}──────────►│ │
│ │──open diff view─────────►│
│ │ │──shows diff + UI
│ │ (blocks...) │
│ (waits...) │ │
│ │ │──user clicks Accept
│ │◄─result: SAVED───────────│
│◄─200 SSE {"result":...}───────│ │
IDE MCP Server Copilot CLI
│ │ │
│──user moves cursor │ │
│──selection changed───────────►│ │
│ │──SSE: selection_changed─────►│
│ │ │──updates display
-
Authentication via nonce. Generate a cryptographically random value for each session. Validate it on every HTTP request.
-
open_diffmust block. The MCP server MUST NOT time out onopen_diffcalls. Use a mechanism that waits indefinitely (with an optional long fallback timeout) for the user to accept or reject. -
Debounce push notifications. Both
selection_changedanddiagnostics_changedshould be debounced at ~200ms to avoid flooding the CLI. -
Tool names are immutable. Tool names like
get_vscode_infomust be used exactly as specified. The CLI matches on exact names. -
Server name is
"vscode-copilot-cli". Use this exact string in the MCP server'sinitializeresponse regardless of your IDE.
- Lock file written to
~/.copilot/ide/{uuid}.lockwith all 8 fields - Pipe / socket accepts HTTP/1.1 with nonce authentication
- MCP server name is
"vscode-copilot-cli" - All 6 tools registered with exact names and matching schemas
- Tools declare
execution.taskSupport: "forbidden"(not top-leveltaskSupport) - SSE stream serves chunked
text/event-streamon GET -
selection_changedpushed on editor changes (file switch, cursor move, selection) -
diagnostics_changedpushed when diagnostics change -
open_diffblocks until user action (no premature timeout) - File URIs follow the convention (lowercase drive letter,
%3Acolon on Windows) -
DELETE /mcpsupported for session teardown
{ "socketPath": "\\\\.\\pipe\\mcp-{guid}.sock", // Windows named pipe // or: "/tmp/mcp-{guid}.sock", // Unix domain socket "scheme": "pipe", "headers": { "Authorization": "Nonce {guid}" }, "pid": 12345, "ideName": "Your IDE Name", "timestamp": 1709836800000, "workspaceFolders": [ "/home/user/my-project" ], "isTrusted": true }