Skip to main content
Glama

dashhound

A dashcam for your dev browser. Named for dogfooding (and for a dog).

You're using your own app and something breaks. You try again and it works fine. You weren't screen recording, the reload cleared the console, and localhost has no Datadog to point at it. dashhound is the dashcam that was already rolling: a local log of what happened on the sites you choose (pages, clicks, what you typed, API calls with what they sent and got back, errors) and a screenshot when something goes wrong. When the bug shows up, you, or your coding agent, look back instead of trying to reproduce it.

The story behind it, and Dash the pixel hound: carlosaguirre.workers.dev/work/dashhound

14:02:11 tab 3 page    http://localhost:3000/cart  Your cart
14:02:15 tab 3 field   Quantity = '0' on /cart
14:02:18 tab 3 field   Promo code = 'SPRING' on /cart
14:02:19 tab 3 click   'Apply' (button) on /cart
14:02:19 tab 3 net     POST 422 http://localhost:3000/api/cart/promo
14:02:20 tab 3 shot    HTTP 422 POST /api/cart/promo -> ~/.local/share/dashhound/shots/20260930-140220-3.jpg
14:02:24 tab 3 click   'Checkout' (button) on /cart
14:02:24 tab 3 console uncaught: TypeError: Cannot read properties of undefined (reading 'total')
  • Local only. Everything is written to ~/.local/share/dashhound by a small local program. No account, no server, no upload.

  • Only the sites you list. Default: http://localhost/* and http://127.0.0.1/*. Add your staging or QA hosts in the extension's settings. dashhound's scripts are only injected there; other sites never run them.

  • Private by default. Password and card fields, anything matching your private selectors, values under keys like password, token, secret, authorization, apiKey, session or cookie, any extra body keys you list in settings (one per line: a plain word matches a whole key name ignoring case, /regex/ for patterns), and anything that passes a card-number checksum are stored as [private]. Request headers are never recorded.

  • Loop recording. Like a dashcam: the oldest recording is deleted when either limit is hit, 24 hours or 500 MB by default (both configurable). It is a dashcam, not an archive.

  • Readable by agents. dashhound mcp is an MCP server, so Claude Code, Cursor or any MCP client can ask "what did the user just do, and what failed?" before asking you.

What it records

Kind

When

What

page

the URL changes (single-page app routes included)

URL and title

click

you click

the words on the button or link (aria-label / title / text), or field <name>

field

a field changes and loses focus, or a select/checkbox changes

name = value

submit

a form is submitted

the form's name

net

a fetch or XMLHttpRequest finishes

method, status (or error), URL, time, and the request and response bodies (text only, redacted, 32 KB each by default)

console

console.error, an uncaught error, an unhandled rejection

the message

shot

after a save-type click (save, add, confirm, delete, submit, pay…), a form submit, HTTP ≥ 400, or a console error

a JPEG of the tab, at most one every 4 s, only of the tab on screen

Shadow DOM is fine: clicks and fields inside open shadow roots are named from the real element.

Related MCP server: peek

Install

Needs Python 3.8+ (standard library only).

With pipx, no clone:

pipx install git+https://github.com/Lucer9/dashhound
dashhound install            # registers the local host with Firefox
dashhound pack               # builds ./dist/dashhound.xpi in the current folder

Or from a clone (the commands below are written for this way; with pipx drop the ./):

git clone https://github.com/Lucer9/dashhound ~/dashhound && cd ~/dashhound
./dashhound install          # registers the local host with Firefox
./dashhound pack             # builds dist/dashhound.xpi

Either way, install writes a small launcher, ~/.local/share/dashhound/dashhound-host, that runs dashhound with the Python it was installed for, and registers that with the browser. Run install again if you move the clone or reinstall with a different Python. Unpacked Chrome loading needs a folder: with pipx the extension is at <pipx venv>/share/dashhound/extension, or unzip the .xpi.

Firefox: until dashhound is signed on addons.mozilla.org, load it in Firefox Developer Edition or Nightly with xpinstall.signatures.required = false (about:config), then Install Add-on From File → dist/dashhound.xpi. Or, for a session, about:debugging → Load Temporary Add-on → extension/manifest.json.

Chrome / Edge / Brave: chrome://extensions → Developer mode → Load unpacked → extension/. Copy the id it shows, then ./dashhound install --chrome-id <id>.

Then open the extension's settings to choose which sites to record.

The toolbar button

The toolbar icon is Dash, a pixel dachshund. He wears a red bandana on tabs that are being recorded and takes it off everywhere else, so there is no badge. Click him for the menu:

  • the site, and whether it is Recording, Paused or Not recorded;

  • one action that fits: Record this site (adds <scheme>://<host>/*, any port, then offers to reload, since the scripts only reach pages loaded after the site is listed), Pause or Resume recording (stops everything without touching the list; kept across restarts);

  • what just happened in this tab: the last 5 events with their time, failed calls and errors in red (never field values or bodies; kept in memory for the browser session only);

  • the coat: dapple (default), cream, black (hard to see on dark toolbars), or your own colours for coat, muzzle, ears and bandana;

  • Copy log command (dashhound log --since 10m, to paste for your agent) and Settings.

The dog was drawn in art/icon.json (16×16, one letter per colour). python3 art/export.py writes the store icons and extension/dog.js, which the extension uses to draw him in any colours at runtime.

Reading it

./dashhound log                       # last hour
./dashhound log --since 10m --kind click,net,console
./dashhound log --grep checkout --since 2h
./dashhound log --kind net --bodies   # what each API call sent and got back
./dashhound log --grep expired        # searches bodies too
./dashhound log --json                # raw events
./dashhound shots                     # screenshots with their reason
./dashhound status                    # data folder, size vs. the MB cap, oldest/newest event, last hour, browser connected
./dashhound report --since 30m -o bug.html   # one HTML file to attach to a bug report

report takes the same --since, --grep and --kind as log and prints the path it wrote (default dashhound-report.html). The file has no external assets and opens offline: the timeline, screenshots inline, and each API call's bodies in a collapsible block. The bodies are already redacted, but read the file before you share it.

Deleting recordings

If something sensitive got recorded by mistake:

./dashhound clear --since 10m         # deletes the last 10 minutes of events and screenshots
./dashhound clear                     # deletes everything
./dashhound clear --yes               # no confirmation prompt

clear asks for confirmation unless you pass --yes. Screenshots are matched by the time they were taken. A browser that is recording right now keeps writing new events after the clear.

status shows "connected" when a dashhound host process is running, which means a browser with the extension is open. The MB cap shown is the one the extension last sent; until then it shows the 500 MB default.

For coding agents (MCP)

claude mcp add dashhound -- dashhound mcp                        # Claude Code, installed with pipx
claude mcp add dashhound -- ~/dashhound/dashhound mcp            # Claude Code, from a clone

Any MCP client works the same way: command dashhound (or ~/dashhound/dashhound), argument mcp. Tools:

  • recent_activity(since?, grep?, kinds?, bodies?): the timeline as text, newest last; bodies adds what each API call sent and got back.

  • get_screenshot(file): one of the screenshots listed in the timeline, as an image.

How it works

extension/ is a Manifest V3 extension (Firefox 140+, Chrome, Edge, Brave). The background registers two scripts, only on the sites you list: page.js runs in the page itself and wraps fetch and XMLHttpRequest (reading a copy of each response, so the page's own reading is untouched) and catches console errors; content.js names clicks and field changes. background.js redacts bodies (redact.js), adds page loads, takes screenshots, and streams everything over native messaging to dashhound, which appends one JSON Lines file per hour and runs the loop.

Capturing in the page rather than through the browser's network API is what makes bodies work the same in every browser: Chrome's Manifest V3 cannot read response bodies from an extension without attaching the debugger.

Checks: ./dashhound selftest (writer, loop limits, reader, report, MCP) and node test/redact.test.js (redaction).

Limits

  • Screenshots need the tab to be the visible one in its window (browser rule).

  • Requests made by web workers and service workers are not seen (only the page's own fetch/XHR).

  • Tabs that were open before a site was added to the list start recording after a reload.

  • status finds the running host with ps, so on systems without it (Windows) it reports the browser as "unknown".

  • Pausing stops what is recorded, not the page scripts: they stay injected on listed sites and simply have their events dropped.

  • Safari is not supported yet (its extensions package and talk to native apps differently).

  • Values typed into fields are recorded unless private: keep the site list to development and test environments.

License

MIT

Available Tools

2 tools
get_screenshotB

A screenshot dashhound took (the path comes from a 'shot' line in recent_activity).

ParametersJSON Schema
NameRequiredDescriptionDefault
fileYes

TDQS

B3.1/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description must carry the full behavioral burden. It does not disclose whether the operation is read-only, what it returns, error behavior, or any side effects; it only gives a brief contextual note about the screenshot's source.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single short sentence with no wasted words. It is front-loaded with the resource identity and follows with the source context, though the parenthetical placement is slightly awkward.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple one-parameter getter with no output schema, the description gives basic context but leaves gaps around return format and exact behavior. It is minimally adequate but not complete enough to fully guide invocation without inference.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% for the single `file` parameter, so the description must compensate. It does so by explaining that the path comes from a 'shot' line in recent_activity, adding substantial meaning beyond the bare string schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose3/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description identifies the resource as a screenshot taken by dashhound and connects it to recent_activity, but it does not explicitly state the action (retrieve/fetch) or distinguish the tool's purpose from its sibling beyond the path source. The tool name supplies the verb, so the purpose is inferable but not fully articulated.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It implies that the `file` argument should come from a 'shot' line in recent_activity, which is useful workflow context. However, it does not explicitly state when to use this tool versus alternatives or what conditions make it the right choice.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

recent_activityA

What the user did in their browser recently, oldest first: pages opened, clicks (by the words on the button/link), field changes (name = value; private fields hidden), API calls with status and, on request, their bodies, console errors, and screenshots taken at key moments. Use it before asking the user what they did or what went wrong.

ParametersJSON Schema
NameRequiredDescriptionDefault
grepNoOnly lines containing this text (case-insensitive).
kindsNoOnly these kinds of events.
sinceNoHow far back: 90s, 30m, 2h, 1d. Default 30m.
bodiesNoInclude what each API call sent and got back (secrets already hidden, cut to 2000 chars). Useful with grep or kinds=['net'].

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden. It discloses useful behavioral traits: chronological ordering ('oldest first'), privacy handling ('private fields hidden'), and conditional behavior ('on request, their bodies'). However, it does not mention authentication requirements, rate limits, retention bounds, or the response format, leaving significant gaps for a tool with zero annotation coverage.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single dense sentence followed by a short usage directive, front-loading the tool's purpose and event enumeration. Every clause appears to earn its place by specifying content or format. The length is justified by the breadth of the tool's output, though it could be slightly more scannable.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no annotations and no output schema, the description must carry the full burden. It adequately describes what the tool returns and its ordering, but omits how results are returned (format, pagination, size limits), authentication needs, and how far back history is actually available (the since param defaults to 30m but no maximum is stated).

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all four parameters fully. The description adds context about what the event types look like (click format, field-change format), which helps interpret the kinds enum and pick grep patterns, but it does not directly clarify the grep, since, or bodies parameters beyond what the schema states. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a clear verb+resource ('What the user did in their browser recently') and enumerates the specific event types returned. It is specific and distinguishable, but does not differentiate from the sibling get_screenshot tool, and its mention of screenshots as an event type could create some ambiguity with that sibling.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit usage context: 'Use it before asking the user what they did or what went wrong.' This tells the agent when to reach for the tool proactively. However, it offers no exclusions, prerequisites, or comparison to the get_screenshot alternative.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 2 tool updatesv0.5.1
    • First observedget_screenshot
    • First observedrecent_activity

TDQS

A3.5/5.0

Scored across 2 tools

Disambiguation5/5

recent_activity retrieves a chronological activity log, while get_screenshot retrieves a specific image by path. Their purposes and inputs are clearly distinct, so an agent can easily choose between them.

Naming Consistency4/5

Both names use snake_case and are readable, but patterns differ slightly: get_screenshot follows verb_noun while recent_activity is a noun phrase without an explicit verb. This is a minor deviation in an otherwise consistent two-tool set.

Tool Count3/5

Two tools is thin for a server whose domain involves browser session inspection; the set feels borderline rather than well-scoped. It may be sufficient for a narrowly focused recorder, but there is little room for filtering or session management.

Completeness4/5

The core observational workflow is covered: recent_activity exposes pages, clicks, field changes, API calls, console errors, and screenshot references, and get_screenshot retrieves those images. Minor gaps exist around querying or filtering activities or managing multiple sessions, but agents can work around them.

Maintenance

ActivityMaintained
ResponsivenessUnresponsive

Related MCP Connectors

Related MCP Servers