Skip to main content
Get up and running with the ACP Dart SDK quickly by building a simple agent.

Prerequisites

Before you begin, ensure you have:
  • Dart SDK 3.9.2 or later installed
  • A code editor (VS Code, IntelliJ, or Android Studio)
  • Basic familiarity with Dart async programming

Choose Your Path

Build an Agent

Create an AI agent that processes prompts and executes tools

Build a Client

Create a client application that connects to an agent

Build an Agent

Follow these steps to create a basic ACP agent.
1

Create a new Dart project

dart create -t console my_agent
cd my_agent
2

Add the ACP Dart SDK dependency

Add to your pubspec.yaml:
pubspec.yaml
dependencies:
  acp_dart: ^0.3.0
Then run:
dart pub get
3

Implement the Agent interface

Create lib/my_agent.dart:
lib/my_agent.dart
import 'dart:async';
import 'package:acp_dart/acp_dart.dart';

class MyAgent implements Agent {
  final AgentSideConnection _connection;
  final Map<String, String> _sessions = {};

  MyAgent(this._connection);

  @override
  Future<InitializeResponse> initialize(InitializeRequest params) async {
    return InitializeResponse(
      protocolVersion: 1,
      agentCapabilities: AgentCapabilities(loadSession: false),
      authMethods: const [],
    );
  }

  @override
  Future<NewSessionResponse> newSession(NewSessionRequest params) async {
    final sessionId = 'session-${DateTime.now().millisecondsSinceEpoch}';
    _sessions[sessionId] = sessionId;

    return NewSessionResponse(
      sessionId: sessionId,
      modes: SessionModeState(
        availableModes: [SessionMode(id: 'default', name: 'Default')],
        currentModeId: 'default',
      ),
    );
  }

  @override
  Future<PromptResponse> prompt(PromptRequest params) async {
    // Send user message update
    await _connection.sessionUpdate(SessionNotification(
      sessionId: params.sessionId,
      update: UserMessageChunkUpdate(
        content: [TextContent(text: params.message)],
      ),
    ));

    // Send agent response
    await _connection.sessionUpdate(SessionNotification(
      sessionId: params.sessionId,
      update: AgentMessageChunkUpdate(
        content: [TextContent(text: 'Hello! I received: ${params.message}')],
      ),
    ));

    return PromptResponse(stopReason: StopReason.completed);
  }

  @override
  Future<void> cancel(CancelNotification params) async {
    // Handle cancellation
  }

  // Stub implementations for optional methods
  @override
  Future<LoadSessionResponse>? loadSession(LoadSessionRequest params) => null;

  @override
  Future<SetSessionModeResponse?>? setSessionMode(
    SetSessionModeRequest params,
  ) async =>
      SetSessionModeResponse();

  @override
  Future<SetSessionConfigOptionResponse>? setSessionConfigOption(
    SetSessionConfigOptionRequest params,
  ) =>
      null;

  @override
  Future<AuthenticateResponse?>? authenticate(
    AuthenticateRequest params,
  ) async =>
      AuthenticateResponse();

  @override
  Future<SetSessionModelResponse?>? setSessionModel(
    SetSessionModelRequest params,
  ) async =>
      SetSessionModelResponse();

  @override
  Future<Map<String, dynamic>>? extMethod(
    String method,
    Map<String, dynamic> params,
  ) =>
      null;

  @override
  Future<void>? extNotification(String method, Map<String, dynamic> params) =>
      null;
}
4

Create the main entry point

Update bin/my_agent.dart:
bin/my_agent.dart
import 'dart:io';
import 'package:acp_dart/acp_dart.dart';
import '../lib/my_agent.dart';

void main() {
  // Create NDJSON stream from stdin/stdout
  final stream = ndJsonStream(stdin, stdout);

  // Create agent connection
  final connection = AgentSideConnection(
    (conn) => MyAgent(conn),
    stream,
  );

  // Agent now handles all ACP communication
  stderr.writeln('Agent started and listening...');
}
5

Run your agent

dart run bin/my_agent.dart
Your agent is now running and listening for ACP messages on stdin!

Test Your Agent

You can test your agent by sending JSON-RPC messages via stdin. Try sending an initialization request:
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":1,"capabilities":{"fs":{"readTextFile":true}}}}
Use a proper ACP client (like Zed or the client example below) for a better testing experience.

Build a Client

Follow these steps to create a basic ACP client that connects to an agent.
1

Create a new Dart project

dart create -t console my_client
cd my_client
2

Add the ACP Dart SDK dependency

Add to your pubspec.yaml:
pubspec.yaml
dependencies:
  acp_dart: ^0.3.0
Then run:
dart pub get
3

Implement the Client interface

Create lib/my_client.dart:
lib/my_client.dart
import 'dart:async';
import 'dart:io';
import 'package:acp_dart/acp_dart.dart';

class MyClient implements Client {
  @override
  Future<RequestPermissionResponse> requestPermission(
    RequestPermissionRequest params,
  ) async {
    // Always allow for this simple example
    return RequestPermissionResponse(
      optionId: params.options.first.id,
    );
  }

  @override
  Future<void> sessionUpdate(SessionNotification params) async {
    // Handle session updates
    final update = params.update;
    
    if (update is UserMessageChunkUpdate) {
      print('User: ${(update.content.first as TextContent).text}');
    } else if (update is AgentMessageChunkUpdate) {
      print('Agent: ${(update.content.first as TextContent).text}');
    } else if (update is ToolCallUpdate) {
      print('Tool Call: ${update.call.name}');
    }
  }

  @override
  Future<ReadTextFileResponse>? readTextFile(
    ReadTextFileRequest params,
  ) async {
    final file = File(params.path);
    if (!file.existsSync()) {
      throw RequestError.resourceNotFound(params.path);
    }
    final content = await file.readAsString();
    return ReadTextFileResponse(content: content);
  }

  @override
  Future<WriteTextFileResponse>? writeTextFile(
    WriteTextFileRequest params,
  ) async {
    final file = File(params.path);
    await file.writeAsString(params.content);
    return WriteTextFileResponse();
  }

  // Stub implementations for optional methods
  @override
  Future<CreateTerminalResponse>? createTerminal(
    CreateTerminalRequest params,
  ) =>
      null;

  @override
  Future<TerminalOutputResponse>? terminalOutput(
    TerminalOutputRequest params,
  ) =>
      null;

  @override
  Future<ReleaseTerminalResponse?>? releaseTerminal(
    ReleaseTerminalRequest params,
  ) =>
      null;

  @override
  Future<WaitForTerminalExitResponse>? waitForTerminalExit(
    WaitForTerminalExitRequest params,
  ) =>
      null;

  @override
  Future<KillTerminalCommandResponse?>? killTerminal(
    KillTerminalCommandRequest params,
  ) =>
      null;

  @override
  Future<Map<String, dynamic>>? extMethod(
    String method,
    Map<String, dynamic> params,
  ) =>
      null;

  @override
  Future<void>? extNotification(String method, Map<String, dynamic> params) =>
      null;
}
4

Create the main entry point

Update bin/my_client.dart:
bin/my_client.dart
import 'dart:io';
import 'package:acp_dart/acp_dart.dart';
import '../lib/my_client.dart';

void main() async {
  // Spawn the agent as a subprocess
  final process = await Process.start('dart', ['run', 'path/to/agent.dart']);

  // Create stream from process stdio
  final stream = ndJsonStream(process.stdout, process.stdin);

  // Create client connection
  final connection = ClientSideConnection(
    (conn) => MyClient(),
    stream,
  );

  // Initialize the connection
  final initResponse = await connection.initialize(InitializeRequest(
    protocolVersion: 1,
    capabilities: ClientCapabilities(
      fs: FilesystemCapabilities(
        readTextFile: true,
        writeTextFile: true,
      ),
    ),
  ));
  print('Initialized with protocol version: ${initResponse.protocolVersion}');

  // Create a session
  final sessionResponse = await connection.newSession(NewSessionRequest(
    mcpServers: [],
  ));
  print('Created session: ${sessionResponse.sessionId}');

  // Send a prompt
  final promptResponse = await connection.prompt(PromptRequest(
    sessionId: sessionResponse.sessionId,
    message: 'Hello, Agent!',
  ));
  print('Prompt completed with reason: ${promptResponse.stopReason}');

  // Clean up
  await process.kill();
}
5

Run your client

dart run bin/my_client.dart
Your client will connect to the agent, initialize, create a session, and send a prompt!

Next Steps

Core Concepts

Learn about the ACP protocol architecture

Building Agents

Comprehensive guide to implementing agents

Building Clients

Comprehensive guide to implementing clients

Examples

Explore complete working examples

Common Issues

Ensure your agent is reading from stdin and writing to stdout. The ndJsonStream function expects newline-delimited JSON messages.
Make sure you’ve imported package:acp_dart/acp_dart.dart and that all method signatures match the interface definitions exactly.
Check that both sides are using the same protocol version and that your agent’s initialize method returns quickly.

What You’ve Learned

  • How to add the ACP Dart SDK to your project
  • How to implement the Agent interface for creating agents
  • How to implement the Client interface for creating clients
  • How to use ndJsonStream for stdio communication
  • How to initialize connections and create sessions
Ready to build more advanced features? Check out the guides for detailed patterns like error handling, terminal operations, and file system access.

Build docs developers (and LLMs) love