Skip to content

Latest commit

 

History

History
771 lines (593 loc) · 25.7 KB

File metadata and controls

771 lines (593 loc) · 25.7 KB

Copilot CLI /ide Protocol

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.

Overview

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.


1. Discovery — Lock Files

Location

~/.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.

Lock File Schema

{
  "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
}
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.

Lifecycle

  • 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.

Pipe / Socket Name Convention

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.

IDE Display Colors

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.


2. Transport — Streamable HTTP over Named Pipe

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.

Authentication

Every HTTP request MUST include the Authorization header from the lock file:

Authorization: Nonce {guid}

Requests without a valid nonce receive 401 Unauthorized.

HTTP Headers

Required on all requests:

  • Authorization: Nonce {guid} — authentication token from lock file

Observed headers on POST requests (VS Code captures):

  • Content-Type: application/json
  • Content-Length: {bytes} or Transfer-Encoding: chunked — the CLI typically uses chunked encoding; servers MUST accept both framing methods
  • Mcp-Session-Id: {sessionId} — associates the request with the active MCP session
  • mcp-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 UUID
  • X-Copilot-PID — CLI process ID
  • X-Copilot-Parent-PID — Parent process ID

Endpoints

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 — Tool Calls and Requests

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).

GET — Server-Sent Events (SSE) Stream

The CLI opens an SSE stream to receive push notifications:

GET /mcp HTTP/1.1
Authorization: Nonce abc123
Mcp-Session-Id: session-a1b2c3d4
HTTP/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: chunked

The server sends chunked SSE events for push notifications (see §4). This connection is kept alive until the session ends or the pipe disconnects.

DELETE — Session Termination

DELETE /mcp HTTP/1.1
Authorization: Nonce abc123
Mcp-Session-Id: session-a1b2c3d4

The response body is empty.

Returns 200 OK and closes the connection.

Session ID

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.


3. Application Layer — MCP Tools

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, with version: "0.0.1" and title: "VS Code Copilot CLI".

Server Capabilities

{
  "tools": {
    "listChanged": true
  }
}

Tool Inventory

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

Tool Execution Mode

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 annotations property has been observed on any tool in wire captures. The execution wrapper is the actual mechanism for declaring task support behavior.

Schema variations: VS Code's tool input schemas include "additionalProperties": false and "$schema": "http://json-schema.org/draft-07/schema#" on tools that have parameters.

Tool Call Format

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.


get_vscode_info

Returns information about the IDE instance.

The tool is named get_vscode_info even 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.


get_selection

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 null content (i.e. the MCP result content[0].text is "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.


get_diagnostics

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")

open_diff

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_diff calls. The tool blocks the HTTP response until the user acts. Copilot CLI is expected to handle this long-running call.


close_diff

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.


update_session_name

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.


4. Push Notifications

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.

selection_changed

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.

diagnostics_changed

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.

SSE Wire Format

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

5. File URI Convention

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: not C:)
  • On Windows, colon is percent-encoded as %3A
  • Backslashes are converted to forward slashes
  • File paths in filePath fields use the OS-native format (but lowercase drive letter on Windows for consistency)

6. Sequence Diagrams

Initial Connection

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)       │

Tool Call (get_selection)

Copilot CLI                    MCP Server                     IDE
    │                               │                          │
    │──POST {"method":"tools/call", │                          │
    │   "params":{"name":           │                          │
    │   "get_selection"}}──────────►│                          │
    │                               │──get selection──────────►│
    │                               │◄─selection data──────────│
    │◄─200 SSE {"result":...}───────│                          │

open_diff (Blocking)

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":...}───────│                          │

Push Notification (selection_changed)

IDE                            MCP Server                     Copilot CLI
 │                               │                              │
 │──user moves cursor            │                              │
 │──selection changed───────────►│                              │
 │                               │──SSE: selection_changed─────►│
 │                               │                              │──updates display

7. Implementation Guide

Required Behaviors

  1. Authentication via nonce. Generate a cryptographically random value for each session. Validate it on every HTTP request.

  2. open_diff must block. The MCP server MUST NOT time out on open_diff calls. Use a mechanism that waits indefinitely (with an optional long fallback timeout) for the user to accept or reject.

  3. Debounce push notifications. Both selection_changed and diagnostics_changed should be debounced at ~200ms to avoid flooding the CLI.

  4. Tool names are immutable. Tool names like get_vscode_info must be used exactly as specified. The CLI matches on exact names.

  5. Server name is "vscode-copilot-cli". Use this exact string in the MCP server's initialize response regardless of your IDE.

Protocol Compatibility Checklist

  • Lock file written to ~/.copilot/ide/{uuid}.lock with 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-level taskSupport)
  • SSE stream serves chunked text/event-stream on GET
  • selection_changed pushed on editor changes (file switch, cursor move, selection)
  • diagnostics_changed pushed when diagnostics change
  • open_diff blocks until user action (no premature timeout)
  • File URIs follow the convention (lowercase drive letter, %3A colon on Windows)
  • DELETE /mcp supported for session teardown