The Agent Client Protocol (ACP) is a standardized JSON-RPC protocol for AI agent communication. Goose implements ACP for rich, bidirectional integration with IDEs, desktop applications, and other tools.
Overview
ACP provides:
- Bidirectional communication: Agents can request permissions and receive cancellations
- Rich tool call handling: Detailed status updates, locations, and content
- Session management: Create, load, and resume sessions with full history
- MCP server integration: Dynamically add MCP servers to sessions
- Streaming responses: Real-time updates as the agent works
ACP vs REST API
| Feature | ACP (stdio/WebSocket) | REST API (goose-server) |
|---|
| Use case | IDE plugins, desktop apps, embedded agents | Web apps, simple clients |
| Transport | stdio, WebSocket, HTTP | HTTP only |
| Permissions | Interactive prompts | Not supported |
| Tool locations | File paths with line numbers | Not provided |
| Streaming | JSON-RPC notifications | Server-Sent Events |
| Session resume | Full conversation history | Session ID only |
Use ACP for desktop applications and IDE integrations where you need rich tool feedback and permission prompts. Use the REST API for simpler web-based integrations.
Quick Start
Running ACP Server
# Start Goose as an ACP server on stdio
goose acp --with-builtin developer,memory
# Or programmatically
cargo run -p goose-cli -- acp --with-builtin developer
The server communicates via stdin/stdout using JSON-RPC.
Python Client Example
See test_acp_client.py in the source repository for a complete example:
import subprocess
import json
class AcpClient:
def __init__(self):
self.process = subprocess.Popen(
['goose', 'acp', '--with-builtin', 'developer'],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
text=True
)
self.request_id = 0
def send_request(self, method, params=None):
self.request_id += 1
request = {
"jsonrpc": "2.0",
"method": method,
"id": self.request_id
}
if params:
request["params"] = params
self.process.stdin.write(json.dumps(request) + "\n")
self.process.stdin.flush()
response = json.loads(self.process.stdout.readline())
return response
# Initialize connection
client = AcpClient()
client.send_request("initialize", {
"protocolVersion": "v1",
"clientInfo": {"name": "my-client", "version": "1.0.0"}
})
# Create session
session = client.send_request("session/new", {
"cwd": "/path/to/project"
})
# Send prompt
client.send_request("session/prompt", {
"sessionId": session["result"]["sessionId"],
"prompt": [{"type": "text", "text": "List files"}]
})
Protocol Methods
Connection Lifecycle
initialize
Establish connection and exchange capabilities.
Request:
{
"jsonrpc": "2.0",
"method": "initialize",
"id": 1,
"params": {
"protocolVersion": "v1",
"clientCapabilities": {},
"clientInfo": {
"name": "vscode-goose",
"version": "1.0.0"
}
}
}
Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "v1",
"agentCapabilities": {
"loadSession": true,
"promptCapabilities": {
"image": true,
"audio": false,
"embeddedContext": true
},
"mcpCapabilities": {
"http": true
}
},
"authMethods": [
{
"id": "goose-provider",
"name": "Configure Provider",
"description": "Run `goose configure` to set up your AI provider"
}
]
}
}
Session Management
session/new
Create a new session with optional MCP servers.
Request:
{
"jsonrpc": "2.0",
"method": "session/new",
"id": 2,
"params": {
"cwd": "/home/user/project",
"mcpServers": [
{
"type": "stdio",
"name": "github",
"command": "uvx",
"args": ["github-mcp-server"],
"env": [{"name": "GITHUB_TOKEN", "value": "ghp_..."}]
}
]
}
}
Response:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"models": {
"current": "claude-sonnet-4-20250514",
"available": [
{"id": "claude-sonnet-4-20250514", "name": "Claude Sonnet 4"},
{"id": "gpt-4o", "name": "GPT-4o"}
]
}
}
}
session/load
Load an existing session by ID.
Request:
{
"jsonrpc": "2.0",
"method": "session/load",
"id": 3,
"params": {
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"cwd": "/home/user/project"
}
}
Response:
Returns full conversation history as a series of session/notification notifications, followed by the response:
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"models": { /* same as session/new */ }
}
}
session/prompt
Send a prompt and receive streaming responses.
Request:
{
"jsonrpc": "2.0",
"method": "session/prompt",
"id": 4,
"params": {
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"prompt": [
{"type": "text", "text": "What files are in this directory?"},
{"type": "image", "data": "base64...", "mimeType": "image/png"}
]
}
}
Response:
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"stopReason": "endTurn"
}
}
The response is sent only after all streaming notifications complete. Use notifications to receive real-time updates.
session/cancel
Cancel an in-progress prompt.
Notification:
{
"jsonrpc": "2.0",
"method": "session/cancel",
"params": {
"sessionId": "550e8400-e29b-41d4-a716-446655440000"
}
}
Notifications (Agent → Client)
Notifications stream updates during session/prompt.
session/notification - Agent Message
{
"jsonrpc": "2.0",
"method": "session/notification",
"params": {
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"update": {
"sessionUpdate": "agentMessageChunk",
"chunk": {
"content": {"type": "text", "text": "Let me check the files"}
}
}
}
}
{
"jsonrpc": "2.0",
"method": "session/notification",
"params": {
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"update": {
"sessionUpdate": "toolCall",
"toolCall": {
"id": "call_abc123",
"title": "Developer: List Files",
"status": "pending"
}
}
}
}
Includes file locations for editor integration:
{
"jsonrpc": "2.0",
"method": "session/notification",
"params": {
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"update": {
"sessionUpdate": "toolCallUpdate",
"toolCallUpdate": {
"id": "call_abc123",
"fields": {
"status": "completed",
"content": [
{"type": "text", "text": "Found 5 files"}
],
"locations": [
{"path": "/home/user/project/main.rs", "line": 1}
]
}
}
}
}
}
The locations field enables IDE integrations to highlight or navigate to files modified by tools.
requestPermission - Interactive Approval
For sensitive operations, the agent requests permission:
{
"jsonrpc": "2.0",
"method": "requestPermission",
"id": 100,
"params": {
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"toolCallUpdate": {
"id": "call_xyz789",
"fields": {
"title": "Execute Command",
"status": "pending",
"rawInput": {"command": "rm -rf /"},
"content": [{"type": "text", "text": "Delete all files?"}]
}
},
"options": [
{"id": "allow_always", "name": "Allow Always", "kind": "allowAlways"},
{"id": "allow_once", "name": "Allow Once", "kind": "allowOnce"},
{"id": "reject_once", "name": "Reject Once", "kind": "rejectOnce"},
{"id": "reject_always", "name": "Reject Always", "kind": "rejectAlways"}
]
}
}
Client Response:
{
"jsonrpc": "2.0",
"id": 100,
"result": {
"outcome": {
"type": "selected",
"optionId": "allow_once"
}
}
}
Custom Methods
Goose extends ACP with custom methods for additional functionality.
_extensions/add
Add an MCP extension to a running session:
{
"jsonrpc": "2.0",
"method": "_extensions/add",
"id": 5,
"params": {
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"config": {
"type": "stdio",
"name": "slack",
"cmd": "uvx",
"args": ["mcp-slack"]
}
}
}
_session/list
List all sessions:
{
"jsonrpc": "2.0",
"method": "_session/list",
"id": 6
}
Response:
{
"jsonrpc": "2.0",
"id": 6,
"result": {
"sessions": [
{
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"title": "My Project",
"cwd": "/home/user/project",
"updatedAt": "2026-03-04T12:00:00Z"
}
]
}
}
Other Custom Methods
_session/get: Get full session details
_session/delete: Delete a session
_session/export: Export session data
_session/import: Import session data
_extensions/remove: Remove an extension
_tools: List available tools
_resource/read: Read an MCP resource
_working_dir/update: Change session working directory
_config/extensions: Get extension configuration
Transport Options
stdio (Default)
Communicate via standard input/output:
goose acp --with-builtin developer
Ideal for:
- CLI tools
- Editor plugins (VS Code, Neovim)
- Process-based integrations
WebSocket (Future)
WebSocket transport for browser-based clients:
goose acp --transport websocket --port 8080
HTTP (Future)
HTTP transport for stateless clients:
goose acp --transport http --port 8080
Implementation Details
Source Code
- ACP server:
crates/goose-acp/src/server.rs
- CLI integration:
crates/goose-cli/src/cli.rs (Command::Acp)
- Protocol library:
sacp crate (vendored)
- Custom methods:
crates/goose-acp/src/custom_requests.rs
- Test client:
test_acp_client.py
Goose automatically extracts file locations from tool responses for developer tools:
// crates/goose-acp/src/server.rs
fn extract_tool_locations(
tool_request: &ToolRequest,
tool_response: &ToolResponse,
) -> Vec<ToolCallLocation> {
// Parse tool output for file paths and line numbers
// Returns locations for IDE navigation
}
Supported tools:
write: File creation (line 1)
edit: File modification (parsed from output)
view: File viewing (parsed from output)
Session State Management
ACP sessions are isolated per connection:
struct GooseAcpSession {
agent: Arc<Agent>,
messages: Conversation,
tool_requests: HashMap<String, ToolRequest>,
cancel_token: Option<CancellationToken>,
}
Sessions accumulate until the transport closes.
Best Practices
Error Handling
Handle JSON-RPC errors gracefully:
response = client.send_request("session/prompt", params)
if "error" in response:
print(f"Error {response['error']['code']}: {response['error']['message']}")
# Fallback logic
Notification Buffering
Buffer notifications during streaming:
def collect_response(client, request_id):
notifications = []
while True:
line = client.process.stdout.readline()
msg = json.loads(line)
if "method" in msg: # Notification
notifications.append(msg)
elif msg.get("id") == request_id: # Response
return msg, notifications
Cancellation
Implement cancellation for long-running operations:
import threading
def prompt_with_timeout(client, session_id, prompt, timeout=30):
def cancel():
time.sleep(timeout)
client.send_notification("session/cancel", {"sessionId": session_id})
threading.Thread(target=cancel, daemon=True).start()
return client.send_request("session/prompt", {...})
Resources
- ACP Specification: Anthropic Cookbook
- Goose ACP Implementation:
crates/goose-acp/
- Test Client:
test_acp_client.py
- sacp Library:
vendor/sacp/ (Rust ACP implementation)