Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Auditing and the watch viewer

execkit can record everything an agent does in the shell, and let you watch it live.

The audit log

Point the server at a destination and every open / exec / close event is appended as JSON, with the session id, transport, an epoch-millisecond timestamp, and the command plus its (redacted, bounded) output.

  • EXECKIT_MCP_AUDIT=/var/log/execkit.jsonl writes one shared file for all sessions.
  • EXECKIT_MCP_AUDIT_DIR=/var/log/execkit/ writes one file per session, named <session_id>-<open_ms>.jsonl. This mode takes precedence when both are set.
  • EXECKIT_MCP_AUDIT_RETENTION_DAYS (default 14, 0 disables) prunes per-session files older than N days at startup. Files with a future-skewed mtime are never deleted.

The audit destination is operator-chosen and never a tool argument, so an injected agent cannot redirect or suppress it.

The watch viewer

Point watch at the audit file or directory from another terminal:

execkit-mcp watch /var/log/execkit.jsonl   # or: execkit-mcp watch  (uses $EXECKIT_MCP_AUDIT)
execkit-mcp watch /var/log/execkit/        # a directory; uses $EXECKIT_MCP_AUDIT_DIR

It is a live, read-only TUI: the agent’s sessions on the left, the selected session’s shell transcript on the right (prompt, command, stdout, stderr in red, exit status), rendered like a normal shell rather than JSON. Command, opened, closed and blocked lines start with their local time ([HH:MM:SS], following $TZ), with a -- YYYY-MM-DD -- line when the date changes, as in the browser viewer. Switch sessions with 1-9 or the arrow keys, scroll with PgUp/PgDn, quit with q. It only ever reads the log. Because the data comes from the server, it works the same under any MCP client.

Browser viewer

For a richer view in a normal browser tab, serve the transcript as a local web page:

execkit-mcp watch --serve /var/log/execkit/        # prints a loopback URL with a token
execkit-mcp watch --serve --open /var/log/execkit/ # ...and open it in your browser

The MCP server also starts the viewer automatically when EXECKIT_MCP_WATCH_WEB is set: it binds 127.0.0.1 only, prints the tokened URL and pushes it to the client as a notification, and keeps the URL stable across restarts so an open tab reconnects. EXECKIT_MCP_WATCH_PORT sets the port (default 7878, falls back to a random one if taken) and EXECKIT_MCP_WATCH_OPEN also opens the browser for you.

The page is read-only and local by construction: it binds loopback only, every request needs the URL token, and it can read the audit stream but never touch a session, a command, or your files. What you get:

  • Sidebar grouped by transport (local / ssh / docker), then by host or target inside each transport (the ssh host or alias such as etlstage, the docker container, or local). Each group header shows its session count. Every session gets its own row: start time (with the date when it is not today), #<n>, the user@host label or your alias, the command count, and a green (live) or grey (closed) dot. The History list uses the same grouping.
  • Timestamps: each command line starts with the local time it started, for example [19:19:22] /tmp $ echo hi; hover it for the full date, time and timezone offset. The audit log records an exec event when the command finishes, so the viewer shows ts - duration_ms. The opened, closed and blocked lines show their event time. A -- YYYY-MM-DD -- line marks a date change, and the first one also appears at the top when a session did not start today. Exports and screenshots include the same times (JSON exports keep the raw ts in unix milliseconds plus the local time).
  • Colored transcript with a header legend (cmd / out / err / ok). Click a legend item to show or hide that line type.
  • Search: press / to find within the transcript, step matches with Enter / Shift+Enter, and press e to jump to the next error or blocked line.
  • History of past sessions, with relative times, when EXECKIT_MCP_AUDIT_DIR is set. Sessions are grouped by transport, then by host, and listed newest first within each host. It shows the 20 newest sessions plus any you kept. It sits at the bottom of the sidebar, collapsed to a History (N) header, or History (20 of 57) when not every session is listed; click the header to expand it (the viewer remembers your choice), then click a session to read its transcript. With a single EXECKIT_MCP_AUDIT file there is no per-session history.
  • Per-session actions from a 3-dots menu: rename (a display alias), pin, keep, export to .txt / .log / .md / .json, and screenshot to .png.

  • A status bar that shows the selected session’s details; click it to copy the session id.

Rename / pin / keep and the sidebar width persist in ~/.execkit/viewer-state.json (mode 0600). That file is the viewer’s only write surface: display metadata only, never able to affect a session, a command, or the audit log.

Headless follow mode

For a pipeable, no-TTY view, use --follow instead of the TUI. It prints each command and its output as a line prefixed with the session id, as it happens. Command, opened, closed and blocked lines carry their local time, and a -- YYYY-MM-DD -- line appears first when the events are not from today and again whenever the date changes:

execkit-mcp watch --follow /var/log/execkit/
# [a3f9-1_local] [14:02:11] /home/u $ npm run build
# [a3f9-1_local] x exit 1  (3420ms)
# [a3f9-2_ssh_deploy@web-01] [14:02:40] /srv $ systemctl restart app

Live notifications in the client

Even with no audit log configured, the server streams each command to the MCP client as it runs, so a host agent can surface its own shell activity without anyone opening a separate terminal. Every session_exec emits:

  • a log notification (notifications/message) carrying the full shell transcript, info on success and warning on a non-zero exit; and
  • a progress notification (notifications/progress) with a one-line summary, when the call supplied a progressToken.

This reveals nothing new: the client already receives the same output in the tool result, redacted and bounded. How the activity is surfaced is up to the client.