Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Control protocol (CCP)

The Copperline Control Protocol (CCP) is a versioned JSON-RPC 2.0 interface over TCP for programmatic control of the emulator. It allows scripts, developer tools, CI runners, and automated agents to inspect state, set breakpoints, step execution, inject input events, change media, and capture framebuffers.

Starting the control server

# Headless mode (server owns execution; paused at reset):
./target/release/copperline --config kick13.example.toml --noaudio \
    --control :0 --control-info /tmp/ccp.json

# Windowed mode (attaches control server to interactive session):
./target/release/copperline --config kick13.example.toml --control-gui :7710

--control-gui can share the window with --gdb-gui (see GDB): a machine stop answers whichever resumes are pending on both clients, a stop the control client did not request arrives as event.stopped, and reverse_*, memory.last_writer, mouse.to, and state.load are refused while the GDB client’s continue is outstanding (pause first). Either client’s pause ends the other’s run with reason pause.

When --control-info FILE is specified, connection details are written to a JSON file:

{"listen": "127.0.0.1:52114", "token": "1f0c...", "proto": 1}

With --run PROG, either mode arms a one-shot loadseg stop for the program before the machine runs, the break-at-entry --gdb has: the first continue (or, windowed, the boot already under way) stops with reason loadseg the moment the guest OS loads the program, before its first instruction, and segments.list then reports its hunks. A windowed session that reaches the stop before any client attached parks there and tells the first client with event.stopped. The stop fires once; a client wanting every load arms its own loadseg break.

Client usage (copperline-ctl)

Copperline includes a CLI tool (copperline-ctl) to interact with active control sessions:

# Query status
copperline-ctl --info /tmp/ccp.json status

# Add a PC breakpoint
copperline-ctl --info /tmp/ccp.json break.add '{"kind": "pc", "addr": "0xFC0100"}'

# Resume execution (blocks until a breakpoint or stop event occurs)
copperline-ctl --info /tmp/ccp.json continue

# Interactive REPL session
copperline-ctl --info /tmp/ccp.json --repl

# Describe a save state (no session needed) and write out its thumbnail
copperline-ctl state-info /tmp/at120.clstate --thumbnail /tmp/at120.png

A/B divergence finder

copperline-ctl diverge launches two headless sessions (two builds, or one build under two configs) and drives them in lockstep over this protocol, comparing the frame digest, the CPU registers and a server-side RAM digest at every frame, then narrowing the first mismatch to the instruction and, for memory, to the byte. See A/B divergence finder.

Debug adapter

copperline-ctl --dap serves the Debug Adapter Protocol over the same bridge: an IDE debugs a program in the emulator with source-level breakpoints, variables and reverse stepping, while the control protocol underneath stays available from the Debug Console (!status, !beam.get).

MCP server

copperline-ctl --mcp exposes the control protocol over standard I/O as a Model Context Protocol server. This allows AI coding agents in environments like Claude Code or Cursor to drive and inspect the emulator directly via structured tool calls. Protocol methods map directly to MCP tools alongside session management utilities.

# Unattached: the agent launches or attaches a session with session tools.
copperline-ctl --mcp

# Attached at startup to a running control server:
copperline-ctl --mcp --info /tmp/ccp.json
copperline-ctl --mcp --connect 127.0.0.1:7710 --token HEX

Claude Code registers it with one command:

claude mcp add copperline -- copperline-ctl --mcp

or via .mcp.json in a project:

{
  "mcpServers": {
    "copperline": {
      "command": "copperline-ctl",
      "args": ["--mcp"],
      "env": {"COPPERLINE_BIN": "/path/to/copperline"}
    }
  }
}

initialize returns an instructions summary of the workflow, and tools/list provides descriptions, JSON schemas, and parameter conventions for all tools.

Tool names

MCP tool names support [a-zA-Z0-9_-], so protocol methods map to tool names with dots replaced by underscores (e.g. warp.get becomes warp_get, media.floppy.insert becomes media_floppy_insert, and capture.screenshot becomes capture_screenshot). Methods without dots (such as status or run_until) retain their exact names. Tool arguments correspond to method parameters (addresses accept hex strings or integers). Protocol errors return with isError: true containing the error code and message. Connection handshake methods (hello, auth) are handled internally by the bridge and not exposed as tools.

Session tools

The bridge manages one active session at a time:

Blocking and wait_ms

Execution methods (continue, run_until, step, step_over, step_out, step_copper, step_frame) accept an optional wait_ms parameter. If the emulated machine does not halt within this time limit (in host milliseconds), the bridge automatically pauses execution and returns the stop event with bridge.paused_after_ms set. Without wait_ms, calls block until a breakpoint or stop condition is reached.

Events

An internal reader thread maintains an event queue (up to 1,024 items) to collect asynchronous events during execution. Use events_next to retrieve individual events with a timeout or events_drain to retrieve all queued notifications. Both report queue depth and dropped event counts.

Screenshots

capture_screenshot returns the PNG image as an MCP image content block (image/png) alongside text output. If path is omitted, a temporary file is used and deleted after reading. When path is provided, the image is saved to that path (relative paths resolve against copperline-ctl’s working directory).

Protocol subset

MCP 2025-06-18 over stdio (newline-delimited JSON-RPC 2.0): supports initialize, notifications/initialized, ping, tools/list, and tools/call. Invalid requests return standard JSON-RPC error codes (-32600, -32700, -32601). Standard output is reserved strictly for protocol messages; diagnostic logs are sent to stderr. The server exits upon EOF on stdin.

Protocol overview

Example stop event payload

{
  "reason": "breakpoint",
  "detail": "Breakpoint at $FC0100",
  "pc": 16515328,
  "frame": 122,
  "vpos": 44,
  "hpos": 101,
  "cck": 8712345,
  "seconds": 2.456,
  "retired_instructions": 1745210
}

Streaming observability

An authenticated client can subscribe to asynchronous event notifications:

events.subscribe {"events":["frame","serial","interrupt","media","debug","bus"],"frame_interval":50,"frame_digest":true}
events.list
events.unsubscribe {"events":["serial"]}

Event types

Command reference summary

Session management

Execution control

Speed

Reverse execution

State inspection and modification

Windowed UI

Diagnostics and profiling

Breakpoints and traps

Input injection

Media management

State snapshot files

The same card is printed without a session by copperline-ctl state-info STATE.clstate [--thumbnail FILE.png], as pretty-printed JSON with the same fields.

Framebuffer capture

Streaming events