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.
Create a new Dart project
dart create -t console my_agent
cd my_agent
Add the ACP Dart SDK dependency
Add to your pubspec.yaml:dependencies:
acp_dart: ^0.3.0
Then run: Implement the Agent interface
Create 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;
}
Create the main entry point
Update 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...');
}
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.
Create a new Dart project
dart create -t console my_client
cd my_client
Add the ACP Dart SDK dependency
Add to your pubspec.yaml:dependencies:
acp_dart: ^0.3.0
Then run: Implement the Client interface
Create 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;
}
Create the main entry point
Update 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();
}
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
Agent not receiving messages
Ensure your agent is reading from stdin and writing to stdout. The ndJsonStream function expects newline-delimited JSON messages.
Type errors when implementing interfaces
Make sure you’ve imported package:acp_dart/acp_dart.dart and that all method signatures match the interface definitions exactly.
Connection hangs or times out
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.