mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 14:10:37 -07:00
268 lines
10 KiB
Markdown
268 lines
10 KiB
Markdown
# Gemini CLI companion plugin: Interface specification
|
|
|
|
> Last Updated: September 15, 2025
|
|
|
|
This document defines the contract for building a companion plugin to enable
|
|
Gemini CLI's IDE mode. For VS Code, these features (native diffing, context
|
|
awareness) are provided by the official extension
|
|
([marketplace](https://marketplace.visualstudio.com/items?itemName=Google.gemini-cli-vscode-ide-companion)).
|
|
This specification is for contributors who wish to bring similar functionality
|
|
to other editors like JetBrains IDEs, Sublime Text, etc.
|
|
|
|
## I. The communication interface
|
|
|
|
Gemini CLI and the IDE plugin communicate through a local communication channel.
|
|
|
|
### 1. Transport layer: MCP over HTTP
|
|
|
|
The plugin **MUST** run a local HTTP server that implements the **Model Context
|
|
Protocol (MCP)**.
|
|
|
|
- **Protocol:** The server must be a valid MCP server. We recommend using an
|
|
existing MCP SDK for your language of choice if available.
|
|
- **Endpoint:** The server should expose a single endpoint (e.g., `/mcp`) for
|
|
all MCP communication.
|
|
- **Port:** The server **MUST** listen on a dynamically assigned port (i.e.,
|
|
listen on port `0`).
|
|
|
|
### 2. Discovery mechanism: The port file
|
|
|
|
For Gemini CLI to connect, it needs to discover which IDE instance it's running
|
|
in and what port your server is using. The plugin **MUST** facilitate this by
|
|
creating a "discovery file."
|
|
|
|
- **How the CLI finds the file:** The CLI determines the Process ID (PID) of the
|
|
IDE it's running in by traversing the process tree. It then looks for a
|
|
discovery file that contains this PID in its name.
|
|
- **File location:** The file must be created in a specific directory:
|
|
`os.tmpdir()/gemini/ide/`. Your plugin must create this directory if it
|
|
doesn't exist.
|
|
- **File naming convention:** The filename is critical and **MUST** follow the
|
|
pattern: `gemini-ide-server-${PID}-${PORT}.json`
|
|
- `${PID}`: The process ID of the parent IDE process. Your plugin must
|
|
determine this PID and include it in the filename.
|
|
- `${PORT}`: The port your MCP server is listening on.
|
|
- **File content and workspace validation:** The file **MUST** contain a JSON
|
|
object with the following structure:
|
|
|
|
```json
|
|
{
|
|
"port": 12345,
|
|
"workspacePath": "/path/to/project1:/path/to/project2",
|
|
"authToken": "a-very-secret-token",
|
|
"ideInfo": {
|
|
"name": "vscode",
|
|
"displayName": "VS Code"
|
|
}
|
|
}
|
|
```
|
|
- `port` (number, required): The port of the MCP server.
|
|
- `workspacePath` (string, required): A list of all open workspace root paths,
|
|
delimited by the OS-specific path separator (`:` for Linux/macOS, `;` for
|
|
Windows). The CLI uses this path to ensure it's running in the same project
|
|
folder that's open in the IDE. If the CLI's current working directory is not
|
|
a sub-directory of `workspacePath`, the connection will be rejected. Your
|
|
plugin **MUST** provide the correct, absolute path(s) to the root of the
|
|
open workspace(s).
|
|
- `authToken` (string, required): A secret token for securing the connection.
|
|
The CLI will include this token in an `Authorization: Bearer <token>` header
|
|
on all requests.
|
|
- `ideInfo` (object, required): Information about the IDE.
|
|
- `name` (string, required): A short, lowercase identifier for the IDE
|
|
(e.g., `vscode`, `jetbrains`).
|
|
- `displayName` (string, required): A user-friendly name for the IDE (e.g.,
|
|
`VS Code`, `JetBrains IDE`).
|
|
|
|
- **Authentication:** To secure the connection, the plugin **MUST** generate a
|
|
unique, secret token and include it in the discovery file. The CLI will then
|
|
include this token in the `Authorization` header for all requests to the MCP
|
|
server (e.g., `Authorization: Bearer a-very-secret-token`). Your server
|
|
**MUST** validate this token on every request and reject any that are
|
|
unauthorized.
|
|
- **Tie-breaking with environment variables (recommended):** For the most
|
|
reliable experience, your plugin **SHOULD** both create the discovery file and
|
|
set the `GEMINI_CLI_IDE_SERVER_PORT` environment variable in the integrated
|
|
terminal. The file serves as the primary discovery mechanism, but the
|
|
environment variable is crucial for tie-breaking. If a user has multiple IDE
|
|
windows open for the same workspace, the CLI uses the
|
|
`GEMINI_CLI_IDE_SERVER_PORT` variable to identify and connect to the correct
|
|
window's server.
|
|
|
|
## II. The context interface
|
|
|
|
To enable context awareness, the plugin **MAY** provide the CLI with real-time
|
|
information about the user's activity in the IDE.
|
|
|
|
### `ide/contextUpdate` notification
|
|
|
|
The plugin **MAY** send an `ide/contextUpdate`
|
|
[notification](https://modelcontextprotocol.io/specification/2025-06-18/basic/index#notifications)
|
|
to the CLI whenever the user's context changes.
|
|
|
|
- **Triggering events:** This notification should be sent (with a recommended
|
|
debounce of 50ms) when:
|
|
- A file is opened, closed, or focused.
|
|
- The user's cursor position or text selection changes in the active file.
|
|
- **Payload (`IdeContext`):** The notification parameters **MUST** be an
|
|
`IdeContext` object:
|
|
|
|
```typescript
|
|
interface IdeContext {
|
|
workspaceState?: {
|
|
openFiles?: File[];
|
|
isTrusted?: boolean;
|
|
};
|
|
}
|
|
|
|
interface File {
|
|
// Absolute path to the file
|
|
path: string;
|
|
// Last focused Unix timestamp (for ordering)
|
|
timestamp: number;
|
|
// True if this is the currently focused file
|
|
isActive?: boolean;
|
|
cursor?: {
|
|
// 1-based line number
|
|
line: number;
|
|
// 1-based character number
|
|
character: number;
|
|
};
|
|
// The text currently selected by the user
|
|
selectedText?: string;
|
|
}
|
|
```
|
|
|
|
**Note:** The `openFiles` list should only include files that exist on disk.
|
|
Virtual files (e.g., unsaved files without a path, editor settings pages)
|
|
**MUST** be excluded.
|
|
|
|
### How the CLI uses this context
|
|
|
|
After receiving the `IdeContext` object, the CLI performs several normalization
|
|
and truncation steps before sending the information to the model.
|
|
|
|
- **File ordering:** The CLI uses the `timestamp` field to determine the most
|
|
recently used files. It sorts the `openFiles` list based on this value.
|
|
Therefore, your plugin **MUST** provide an accurate Unix timestamp for when a
|
|
file was last focused.
|
|
- **Active file:** The CLI considers only the most recent file (after sorting)
|
|
to be the "active" file. It will ignore the `isActive` flag on all other files
|
|
and clear their `cursor` and `selectedText` fields. Your plugin should focus
|
|
on setting `isActive: true` and providing cursor/selection details only for
|
|
the currently focused file.
|
|
- **Truncation:** To manage token limits, the CLI truncates both the file list
|
|
(to 10 files) and the `selectedText` (to 16KB).
|
|
|
|
While the CLI handles the final truncation, it is highly recommended that your
|
|
plugin also limits the amount of context it sends.
|
|
|
|
## III. The diffing interface
|
|
|
|
To enable interactive code modifications, the plugin **MAY** expose a diffing
|
|
interface. This allows the CLI to request that the IDE open a diff view, showing
|
|
proposed changes to a file. The user can then review, edit, and ultimately
|
|
accept or reject these changes directly within the IDE.
|
|
|
|
### `openDiff` tool
|
|
|
|
The plugin **MUST** register an `openDiff` tool on its MCP server.
|
|
|
|
- **Description:** This tool instructs the IDE to open a modifiable diff view
|
|
for a specific file.
|
|
- **Request (`OpenDiffRequest`):** The tool is invoked via a `tools/call`
|
|
request. The `arguments` field within the request's `params` **MUST** be an
|
|
`OpenDiffRequest` object.
|
|
|
|
```typescript
|
|
interface OpenDiffRequest {
|
|
// The absolute path to the file to be diffed.
|
|
filePath: string;
|
|
// The proposed new content for the file.
|
|
newContent: string;
|
|
}
|
|
```
|
|
|
|
- **Response (`CallToolResult`):** The tool **MUST** immediately return a
|
|
`CallToolResult` to acknowledge the request and report whether the diff view
|
|
was successfully opened.
|
|
- On Success: If the diff view was opened successfully, the response **MUST**
|
|
contain empty content (i.e., `content: []`).
|
|
- On Failure: If an error prevented the diff view from opening, the response
|
|
**MUST** have `isError: true` and include a `TextContent` block in the
|
|
`content` array describing the error.
|
|
|
|
The actual outcome of the diff (acceptance or rejection) is communicated
|
|
asynchronously via notifications.
|
|
|
|
### `closeDiff` tool
|
|
|
|
The plugin **MUST** register a `closeDiff` tool on its MCP server.
|
|
|
|
- **Description:** This tool instructs the IDE to close an open diff view for a
|
|
specific file.
|
|
- **Request (`CloseDiffRequest`):** The tool is invoked via a `tools/call`
|
|
request. The `arguments` field within the request's `params` **MUST** be an
|
|
`CloseDiffRequest` object.
|
|
|
|
```typescript
|
|
interface CloseDiffRequest {
|
|
// The absolute path to the file whose diff view should be closed.
|
|
filePath: string;
|
|
}
|
|
```
|
|
|
|
- **Response (`CallToolResult`):** The tool **MUST** return a `CallToolResult`.
|
|
- On Success: If the diff view was closed successfully, the response **MUST**
|
|
include a single **TextContent** block in the content array containing the
|
|
file's final content before closing.
|
|
- On Failure: If an error prevented the diff view from closing, the response
|
|
**MUST** have `isError: true` and include a `TextContent` block in the
|
|
`content` array describing the error.
|
|
|
|
### `ide/diffAccepted` notification
|
|
|
|
When the user accepts the changes in a diff view (e.g., by clicking an "Apply"
|
|
or "Save" button), the plugin **MUST** send an `ide/diffAccepted` notification
|
|
to the CLI.
|
|
|
|
- **Payload:** The notification parameters **MUST** include the file path and
|
|
the final content of the file. The content may differ from the original
|
|
`newContent` if the user made manual edits in the diff view.
|
|
|
|
```typescript
|
|
{
|
|
// The absolute path to the file that was diffed.
|
|
filePath: string;
|
|
// The full content of the file after acceptance.
|
|
content: string;
|
|
}
|
|
```
|
|
|
|
### `ide/diffRejected` notification
|
|
|
|
When the user rejects the changes (e.g., by closing the diff view without
|
|
accepting), the plugin **MUST** send an `ide/diffRejected` notification to the
|
|
CLI.
|
|
|
|
- **Payload:** The notification parameters **MUST** include the file path of the
|
|
rejected diff.
|
|
|
|
```typescript
|
|
{
|
|
// The absolute path to the file that was diffed.
|
|
filePath: string;
|
|
}
|
|
```
|
|
|
|
## IV. The lifecycle interface
|
|
|
|
The plugin **MUST** manage its resources and the discovery file correctly based
|
|
on the IDE's lifecycle.
|
|
|
|
- **On activation (IDE startup/plugin enabled):**
|
|
1. Start the MCP server.
|
|
2. Create the discovery file.
|
|
- **On deactivation (IDE shutdown/plugin disabled):**
|
|
1. Stop the MCP server.
|
|
2. Delete the discovery file.
|