ACP Reference Server — a minimal TypeScript server implementing the Agent Control Protocol.
Note
This is a reference implementation for development, testing, and learning. For production workloads, implement the ACP protocol directly in your language/framework of choice, or see Vocall Engine for a production-grade implementation.
MCP reads. ACP acts. While MCP connects models to data, ACP connects AI agents to existing application user interfaces — letting them navigate screens, fill forms, click buttons, open modals, and confirm destructive actions.
The Agent Control Protocol (ACP) is an open protocol for AI agents to control existing application user interfaces over WebSocket. An ACP-compliant engine (this server) receives a UI manifest from an SDK (the client), then uses an LLM to interpret user requests and send UI commands back.
┌──────────────┐ WebSocket ┌──────────────┐ OpenAI API ┌─────────┐
│ ACP SDK │◄───────────►│ ACP Engine │◄────────────►│ LLM │
│ (your app) │ │ (this server) │ │ │
└──────────────┘ └──────────────┘ └─────────┘
The SDK sends a manifest describing screens, fields, actions, and modals. The engine converts this into LLM tools, processes user text through a streaming agent loop, and sends back UI commands for the SDK to execute.
@acprotocol/server
├── server.ts WebSocket server, connection handling, message routing
├── agent.ts Streaming agent loop (LLM → tools → execute → repeat)
├── session.ts Per-connection state: manifest, history, seq counter
├── prompt.ts System prompt builder from manifest
├── tools.ts Manifest ↔ OpenAI tool conversion
├── types.ts Full ACP v2 type definitions
├── index.ts Public API exports
└── cli.ts CLI entry point
SDK Engine LLM
│ │ │
│◄──── config ────────────────│ │
│───── manifest ─────────────►│ │
│◄──── status: idle ──────────│ │
│◄──── chat: greeting ────────│ │
│ │ │
│───── text ─────────────────►│ │
│◄──── status: thinking ──────│───── stream completion ─────►│
│◄──── chat (delta) ──────────│◄──── delta.content ──────────│
│◄──── chat (delta) ──────────│◄──── delta.content ──────────│
│◄──── status: executing ─────│◄──── delta.tool_calls ───────│
│◄──── command {seq, actions}─│ │
│───── result {seq, results}─►│───── tool results ──────────►│
│◄──── status: thinking ──────│◄──── delta.content ──────────│
│◄──── chat (final) ──────────│ │
│◄──── status: idle ──────────│ │
OPENAI_API_KEY=sk-... npx @acprotocol/serverThe server starts a WebSocket endpoint at ws://localhost:3000/connect.
| Variable | Default | Description |
|---|---|---|
OPENAI_API_KEY |
(required) | API key for the LLM provider |
OPENAI_BASE_URL |
https://api.openai.com/v1 |
Base URL (change for Groq, DeepSeek, etc.) |
ACP_MODEL |
gpt-4o |
Model name |
ACP_PORT |
3000 |
WebSocket port |
# OpenAI
OPENAI_API_KEY=sk-... npx @acprotocol/server
# Groq
OPENAI_API_KEY=gsk-... OPENAI_BASE_URL=https://api.groq.com/openai/v1 \
ACP_MODEL=llama-3.3-70b-versatile npx @acprotocol/server
# DeepSeek
OPENAI_API_KEY=sk-... OPENAI_BASE_URL=https://api.deepseek.com \
ACP_MODEL=deepseek-chat npx @acprotocol/server
# Local (LM Studio / Ollama)
OPENAI_BASE_URL=http://localhost:1234/v1 OPENAI_API_KEY=none \
ACP_MODEL=local npx @acprotocol/serverimport { createServer } from "@acprotocol/server";
import OpenAI from "openai";
const server = createServer({
openai: new OpenAI({ apiKey: "sk-..." }),
model: "gpt-4o",
port: 3000,
});
await server.start();Creates a WebSocket server implementing the ACP protocol.
options.openai— OpenAI client instance (supports any OpenAI-compatible API viabaseURL)options.model— Model name for LLM completionsoptions.port— WebSocket server port- Returns
{ start(), stop() }
Per-connection session state with manifest, history (sliding window of 40 messages), and sequence counter.
const session = new Session("session-id");
session.setManifest(manifest); // stores manifest, builds system prompt
session.addMessage(msg); // adds to history with sliding window
session.getHistory(); // returns a copy of the message history
session.nextSeq(); // returns 0, 1, 2, ...Builds a multi-section LLM system prompt from an ACP manifest. Includes identity, instructions, user context, application context, screen descriptions, and behavioral rules.
Converts an ACP manifest into OpenAI-compatible tool definitions. Generates 6 base tools plus 2 modal tools when modals are present.
Tools: navigate, set_field, clear_field, click_action, ask_confirm, show_toast, open_modal, close_modal
Converts an OpenAI tool call into an ACP UIAction for inclusion in a command message.
The core agent loop. Streams LLM completions, accumulates tool calls, executes UI actions on the client, and maps results back to the LLM. Runs up to 15 rounds before sending a fallback response.
npm test # Run all tests
npm run test:watch # Watch mode
npm run test:coverage # Coverage report (85%+ required)The test suite includes:
- Unit tests — prompt builder, tool conversion, session state management
- Integration tests — agent loop with mock OpenAI, WebSocket server lifecycle
- Conformance tests — AJV validation of all messages against the ACP v2 JSON Schema
npm install
npm run dev # Run with tsx (hot reload)
npm run build # Build with tsup
npm start # Run built version- ACP Specification — the protocol spec and JSON Schema
- ACP Demo — interactive demo (Pet Registration form)
- Live Sandbox — try ACP without installing anything
- ACP Conformance Suite — test fixtures and validators
MCP reads. ACP acts. MCP connects models to data. ACP connects agents to interfaces.
- GitHub Discussions — Questions, ideas, and general discussion
- Issue Tracker — Bug reports and feature requests
- Code of Conduct
- Security Policy
Apache 2.0