---
title: "Copilot CLI ACP server"
shortTitle: ACP server
intro: "Learn about {% data variables.copilot.copilot_cli %}'s Agent Client Protocol server."
versions:
feature: copilot
category:
- Configure Copilot # Copilot discovery page
- Configure Copilot CLI # Copilot CLI bespoke page
contentType: reference
redirect_from:
- /copilot/reference/acp-server
docsTeamMetrics:
- copilot-cli
---
> [!NOTE]
> ACP support in {% data variables.copilot.copilot_cli %} is in {% data variables.release-phases.public_preview %} and subject to change.
## Overview
The Agent Client Protocol (ACP) is a protocol that standardizes communication between clients (such as code editors and IDEs) and agents (such as {% data variables.copilot.copilot_cli_short %}). For more details about this protocol, see the [official introduction](https://agentclientprotocol.com/get-started/introduction).
## Use cases
* **IDE integrations:** Build {% data variables.product.prodname_copilot_short %} support into any editor or development environment.
* **CI/CD pipelines:** Orchestrate agentic coding tasks in automated workflows.
* **Custom frontends:** Create specialized interfaces for specific developer workflows.
* **Multi-agent systems:** Coordinate {% data variables.product.prodname_copilot_short %} with other AI agents using a standard protocol.
## Starting the ACP server
Use the `--acp` option of the `copilot` command to start the CLI's ACP server. You can specify the transport mode with either the `--stdio` or `--port` options. If no transport mode is specified, the server defaults to stdio mode.
ACP mode allows sessions with a configured bring-your-own-key (BYOK) provider (`COPILOT_PROVIDER_*` environment variables, or a providers configuration file—see `COPILOT_PROVIDERS_CONFIG` in [AUTOTITLE](/copilot/reference/copilot-cli-reference/cli-command-reference#environment-variables)) to run without {% data variables.product.github %} login, matching the behavior of `-p`/interactive mode.
### Options applied to every session
The ACP `session/new` request only lets a client set a few session parameters, such as the working directory and the MCP servers to use. It does not carry tool-filtering or reasoning settings. To configure those, pass the corresponding options when you **start the server**. The server stores the values and applies them as the initial configuration for every session it creates or loads, for any client that connects. A connecting client does not choose these values—whoever launches the server does.
| Server option | Accepted value | Effect on every session |
|---------------|----------------|-------------------------|
| `--available-tools=TOOL ...` | A quoted, comma-separated list of tool names | The session can use only the listed tools. |
| `--excluded-tools=TOOL ...` | A quoted, comma-separated list of tool names | The listed tools are removed from the session. |
| `--effort=LEVEL`, `--reasoning-effort=LEVEL` | `low`, `medium`, `high`, `xhigh`, or `max` | Sets the session's initial reasoning effort. |
For example, this command starts a server whose sessions all use maximum reasoning effort and expose only the `bash` and `view` tools:
```bash
copilot --acp --port 3000 --effort=max --available-tools="bash,view"
```
Every session the connected client opens against that server inherits those settings. Because the values are fixed when the server starts, a client cannot change them per session through `session/new`.
### stdio mode
stdio mode is inferred by default when you start the ACP server. You can also use the `--stdio` option for disambiguation.
```bash
copilot --acp --stdio
```
### TCP mode
If the `--port` option is provided in combination with the `--acp` option, the server is started in TCP mode.
```bash
copilot --acp --port 3000
```
### Choosing between stdio and TCP
Both transport modes carry the same ACP messages, encoded as newline-delimited JSON (NDJSON). They differ only in how a client connects to the server and how the server's lifecycle is managed. The two modes are mutually exclusive: passing both `--stdio` and `--port` is rejected.
| Aspect | stdio mode | TCP mode |
|---|---|---|
| **How the client connects** | The client launches `copilot --acp` as a child process and exchanges messages over the process's standard input and output. | The server opens a TCP listener that clients connect to over a network socket. By default it binds to the loopback address `127.0.0.1`. |
| **Number of clients** | A single client—the process that spawned the server and owns the pipe. | The listener accepts socket connections, each handled as its own agent connection. |
| **Lifecycle** | Tied to the parent process. When the input stream closes—because the parent exits or closes the pipe—the server shuts down automatically. | Independent of any single client. The server keeps listening on the port until it is stopped, for example with Ctrl+C. |
| **Standard output** | Reserved for the NDJSON protocol stream, so it can't be used for logs or other text. | Free for other use, because protocol traffic travels over the socket. |
When to use each mode:
* Use **stdio mode** when an editor, IDE, or script spawns {% data variables.copilot.copilot_cli_short %} directly as a subprocess. This is the default and the recommended setup for IDE integration, because the transport is established automatically when the process starts and torn down when it exits.
* Use **TCP mode** when a client needs to reach the server over a socket instead of a pipe—for example, from a separate process or container, or when connecting to a longer-lived server on a known port.
## Example: integrating with the ACP server
The following example is a client application that uses {% data variables.product.prodname_copilot_short %} by interacting with {% data variables.copilot.copilot_cli %}'s ACP server. It starts the ACP server in stdio mode, opens a session, asks you to enter a prompt, sends it, and prints the streamed response.
There is a growing ecosystem of libraries for interacting with ACP servers programmatically. This example uses the [ACP TypeScript library](https://agentclientprotocol.com/libraries/typescript).
To run this example, you need the following dependencies:
* [Node.js](https://nodejs.org) version 18 or later.
* {% data variables.copilot.copilot_cli %}, installed and either authenticated with {% data variables.product.github %} or configured with a BYOK provider (see [Starting the ACP server](#starting-the-acp-server)).
* The `@agentclientprotocol/sdk` package, which provides the ACP TypeScript library. Install it by running `npm install @agentclientprotocol/sdk`.
```typescript copy
import * as acp from "@agentclientprotocol/sdk";
import { spawn } from "node:child_process";
import { Readable, Writable } from "node:stream";
import * as readline from "node:readline/promises";
async function main() {
const executable = process.env.COPILOT_CLI_PATH ?? "copilot";
// ACP uses standard input/output (stdin/stdout) for transport; we pipe these for the NDJSON stream.
const copilotProcess = spawn(executable, ["--acp", "--stdio"], {
stdio: ["pipe", "pipe", "inherit"],
});
if (!copilotProcess.stdin || !copilotProcess.stdout) {
throw new Error("Failed to start Copilot ACP process with piped stdio.");
}
// Create ACP streams (NDJSON over stdio)
const output = Writable.toWeb(copilotProcess.stdin) as WritableStream;
const input = Readable.toWeb(copilotProcess.stdout) as ReadableStream;
const stream = acp.ndJsonStream(output, input);
const client: acp.Client = {
async requestPermission(params) {
// This example should not trigger tool calls; if it does, refuse.
return { outcome: { outcome: "cancelled" } };
},
async sessionUpdate(params) {
const update = params.update;
if (update.sessionUpdate === "agent_message_chunk" && update.content.type === "text") {
process.stdout.write(update.content.text);
}
},
};
const connection = new acp.ClientSideConnection((_agent) => client, stream);
await connection.initialize({
protocolVersion: acp.PROTOCOL_VERSION,
clientCapabilities: {},
});
const sessionResult = await connection.newSession({
cwd: process.cwd(),
mcpServers: [],
});
process.stdout.write("Session started!\n");
// Ask the user to enter a prompt instead of using a hard-coded one.
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const promptText = await rl.question("Enter a prompt: ");
rl.close();
const promptResult = await connection.prompt({
sessionId: sessionResult.sessionId,
prompt: [{ type: "text", text: promptText }],
});
process.stdout.write("\n");
if (promptResult.stopReason !== "end_turn") {
process.stderr.write(`Prompt finished with stopReason=${promptResult.stopReason}\n`);
}
// Best-effort cleanup
copilotProcess.stdin.end();
copilotProcess.kill("SIGTERM");
await new Promise((resolve) => {
copilotProcess.once("exit", () => resolve());
setTimeout(() => resolve(), 2000);
});
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
```
To run the example:
1. Save the code above to a file named `acp-client.ts`.
1. Run the file with `npx tsx`, which runs the TypeScript directly without a separate build step:
```bash
npx tsx acp-client.ts
```
## Using slash commands
{% data variables.copilot.copilot_cli %}'s built-in slash commands can be run over ACP. To invoke one, send it as an ordinary prompt whose text is the command, passed as a single text content block—for example, `/context` or `/session info`. The server recognizes the command and runs it directly: informational commands such as `/usage` or `/context` return their output without invoking the model, while action commands such as `/plan` or `/review` start the corresponding agent task. Either way, the command text is not sent to the model as a question.
### Discovering available commands
The server advertises the commands it supports through the standard ACP `available_commands_update` session notification. It is sent after a session is created or loaded, and again whenever the set changes—for example, when skills finish loading. This advertised list is the authoritative, always-current set of commands you can run over ACP, and clients typically surface it in a command menu.
The advertised list contains:
* **Built-in commands**, such as `/compact`, `/context`, `/usage`, `/env`, `/model`, `/mcp`, `/plan`, `/review`, `/research`, `/session`, and `/rename`.
* **Enabled, user-invocable skills**, which appear as `/SKILL-NAME` commands.
Commands that the client itself registers are not advertised back to it.
### Accessing the list from your client
Because the list arrives as a notification rather than in response to a request, there is no method to fetch it on demand. Your client accesses it by handling the `session/update` notification and reacting to updates whose type is `available_commands_update`. Each entry has a `name` (without the leading slash), a `description`, and an optional `input.hint` that describes the command's arguments. The notification is re-sent whenever the set changes, so treat each one as a complete replacement of any list you have cached.
The following `sessionUpdate` handler captures the advertised commands, extending the `client` object from the example shown earlier.
```typescript copy
// Track the latest advertised commands for the session.
let availableCommands: acp.AvailableCommand[] = [];
const client: acp.Client = {
async sessionUpdate(params) {
const update = params.update;
if (update.sessionUpdate === "available_commands_update") {
// This notification is a full snapshot—replace any cached list.
availableCommands = update.availableCommands;
for (const command of availableCommands) {
// command.name has no leading slash; invoke it by sending "/" as a prompt.
console.log(`/${command.name} — ${command.description}`);
}
return;
}
// ...handle other updates, such as agent_message_chunk
},
// ...other client methods, such as requestPermission
};
```
To run one of the advertised commands, send its name as a prompt in a single text content block—for example, `{ type: "text", text: "/context" }`—as described in [Using slash commands](#using-slash-commands).
### Commands that cannot be used over ACP
Slash commands that depend on the interactive terminal interface are not handled by the ACP server. This includes commands that open a picker, dialog, or full-screen view, such as `/diff`, `/resume`, `/theme`, `/settings`, `/login`, `/help`, `/tasks`, and `/undo`. As a rule, if a command does not appear in the `available_commands_update` list, it will not run over ACP: the server treats the text as an ordinary prompt and forwards it to the model instead of executing it.
Because ACP clients have no interactive pickers, a built-in command that would normally open a submenu instead returns its options as text. Provide the subcommand explicitly to get a direct result—for example, `/session info` or `/mcp list` rather than `/session` or `/mcp` on its own.
For a complete list of slash commands for {% data variables.copilot.copilot_cli_short %}, see [AUTOTITLE](/copilot/reference/copilot-cli-reference/cli-command-reference#slash-commands-in-the-interactive-interface).
## Further reading
* [Official ACP documentation](https://agentclientprotocol.com/protocol/overview)