npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@thehammer/danx-dashboard-mcp

v0.1.85

Published

Stdio MCP server wrapping danxbot's dashboard /api/issues/* normalized DB-backed HTTP routes for dispatched agents (DX-704 Phase 2).

Downloads

1,522

Readme

@thehammer/danx-dashboard-mcp

Stdio MCP server wrapping danxbot's dashboard /api/issues/* normalized DB-backed HTTP routes. Replaces the legacy "agents Edit/Write .yml files directly" pattern from before DX-704 / DX-811.

Each tool is a thin envelope over one HTTP route — Zod-validated at the MCP boundary, fetch under the hood, server response passed back to the agent verbatim. Refusal envelopes ({error, ...extra} with failed_gate, non_terminal_phases, offending_keys, etc.) come through as {ok: false, status, body} so the agent can pick the right next action without guessing. 5xx and network failures throw.

Env vars (required, fail-loud at boot)

| Var | Purpose | |---|---| | DANXBOT_DASHBOARD_URL | Dashboard base, e.g. http://danxbot-dashboard:5555 (inside compose) or http://localhost:5555 (host) | | DANXBOT_DASHBOARD_TOKEN_FILE or DANXBOT_DISPATCH_TOKEN | The credential, declared exactly one of two ways (DX-2862, src/credential.ts). A FILE holding the bearer (a leading ~ is the user's home) — how an interactive operator session declares one, since its .mcp.json is git-tracked and can never carry the token itself. Or the bearer VERBATIM — how a dispatch declares one, substituted from its overlay. A declared file is AUTHORITATIVE and its failure is FATAL: an inherited DANXBOT_DISPATCH_TOKEN is never consulted instead, because on an operator machine that value routinely names a different (local-dev) dashboard, which answers healthily and empty. The same resolver serves the bridge subcommand below, so a session's tools and its event stream can never sign in as two different identities | | DANX_REPO_NAME | Repo half of the qualified board id | | DANXBOT_BOARD_NAME | Board-slug half of the qualified board id (the dispatch injects board.slug here) |

BOARD-ONLY (DX-1171): board is the first-level concept; repo is DERIVED from board server-side, never passed. At boot the package composes the dispatch's qualified board id <repo>:<slug> from DANX_REPO_NAME + DANXBOT_BOARD_NAME and appends it as ?board=<id> (and body.board on create) to every /api/issues/* request. Each tool exposes an optional board arg — a FULL qualified id <repo>:<slug> (e.g. platform:the-supply-operations-hub) — that overrides it per-call for cross-board reads/writes (unknown board → 404). The dashboard owns board→repo resolution; this package never sends repo.

Tool surface

All exposed as mcp__danx-dashboard__<name> once wired through the workspace mcp.template.json.

| Tool | HTTP | Notes | |---|---|---| | issue_list | GET /api/issues | filters: type, parent_id (null → root-only), dispatchable_derived, assigned_agent, include_closed, limit, offset | | issue_get | GET /api/issues/:id or GET /api/issues/batch | Pass id for one card, or ids[] (DX-2727, at most 100) to resolve many across boards in ONE call — global, so ids with board throws; per-id not_found rather than a whole-call 404. Minimal scalars by default; fields opts in | | issue_create | POST /api/issues | Epic REQUIRES non-empty phase_children[] (atomic insert). title = short domain-naming label; summary = 1–3 plain-language sentences, always shown; description = the collapsed "Context" body. Root and every phase child take their own summary | | issue_edit | PATCH /api/issues/:id/edit | Prose + structured keys (title, summary (null clears), description, ac, checklists, effort_level, parent_id, priority, list_id); semantic keys refused with 400 + pointer to dedicated handler. priority (DX-1532) takes a tier word (low/high/…) or a number in [0,6) — the ONLY way to set the numeric column the Trello label + dashboard badge read; never set priority via description prose | | issue_transition | POST /api/issues/:id/transition | Actions: ready, pickup, rollback_pickup, complete, cancel, block, unblock, archive, reopen. block is a dispatch hold only — it never marks the card as needing a human; use issue_problem add for that | | issue_problem | GET/POST/PATCH/DELETE /api/issues/:id/problems[/:pid] | Actions list / add / edit / remove. A problem is one statement the operator must resolve (a question or a plan flaw) with its own solutions and answers; add takes statement + optional solutions[] in one transaction and IS what puts the card in front of a human (open_problem_count > 0) — a successful add also returns problems_reminder: {open_problem_count, instruction}. Edit + remove are hash-guarded (base_hash, 409 stale_problem); removing the card's last open problem is always allowed — it just means the card no longer needs a human | | issue_solution | POST/PATCH/DELETE /api/issues/:id/problems/:pid/solutions[/:sid] | Actions add / edit / remove, problem_id required (list via issue_problem list). Edit + remove are hash-guarded (base_hash, 409 stale_solution with the current row); at most one live recommended per problem; a chosen option cannot be edited. No answer action on any tool — the operator answers in the dashboard | | issue_triage | POST /api/issues/:id/triage | Send {confidence, reason} — an integer 0-5 score; the server computes the verdict (approve/cancel/keep/defer) against the board's configured thresholds (DX-2086). keep/defer now block the card. None of these are a cross-card ordering gate; use issue_dependency to sequence cards | | issue_comment | POST/PATCH/DELETE /api/issues/:id/comments[/:cid] | Author server-stamped, soft-delete preserved | | issue_dependency | POST/DELETE /api/issues/:id/dependencies[/:did] | depends_on cycle-checked; remove hardcodes reason: "recorded_in_error". The only mechanism the dispatch picker enforces to sequence one card after another — status alone is not a substitute | | issue_retire_branch | POST /api/issues/:id/card-branch-retire | Mark this card's own card/<id> branch RETIRED (unsafe to merge) — {reason}, by server-stamped. NO status/terminal gate (DX-2845): settable the instant an audit/split judges the branch unsafe, whatever the card's status. The dispatched worker reads this at its next bootstrap and forces origin/main instead of re-attaching to the retired content | | issue_retro | PUT /api/issues/:id/retro | Requires terminal card; replace semantics. correctable_danxbot_problem + correctable_danxbot_problem_description (DX-2794) are REQUIRED on every write, like tests[] — answer honestly whether THIS dispatch hit a fixable problem in danxbot's own code/configuration (not "the card was hard"); a true+described retro starts exactly one automated repair |

plan_get — cheap by default, opt-in for the rest (DX-2727)

A bare plan_get (no fields) returns only the plan's cheap scalars — plan, boards, cardCount, bucketCounts, status, session, sessionListenerAttached — plus available_field_groups naming what else exists. Pass fields to opt into cards (paged: cards_offset, default 0, and cards_limit, 1..1000, default 200, pick the page; the response carries cards_total and cards_offset, so page with cards_offset while cards_offset + cards.length < cards_total — either paging arg without cards in fields is a 400), records (every goal/rule/caveat) or records:goal / records:rule / records:caveat (just one kind), architecture, and sessions. A plan_get made only to grab a hash before a one-line edit no longer pays for the architecture document or every member card.

plan_list / plan_get — computed status (DX-2834)

Every plan carries a status, computed fresh on every read and never stored — no writer ever sets it:

| Status | Meaning | |---|---| | complete | At least one card, and every card Done or Cancelled. Wins even with no session — a finished plan needs nobody. | | awaiting-session | Not complete, and no session is LIVE on the plan. A plan_sessions row is never released when a session merely ends (only when the plan itself is deleted), so this checks real liveness — last_active_at within the last minute — never just "has a session ever connected". A working session's event stream stamps that activity every 15 seconds on its own, so an open, idle session stays live with no tool calls. | | building | Not complete, a live session is connected, and at least one card is ToDo/In Progress, or carries an unnamed active state (Blocked, Needs Help — i.e. an OPEN problem, DX-2830 — and, once it exists, Verify): work started and is either moving or stuck. | | planning | Everything else: no cards, or only Review/Backlog/terminal cards with at least one not Done/Cancelled. |

Evaluated in that order (complete > awaiting-session > building > planning) — first match wins. plan_list takes an optional status arg to filter to one of them (GET /api/plans?status=); plan_get always returns the one plan's own status, ungated.

bridge — a working session's event stream client

A program runs this, not an agent: the danxbot Claude Code plugin's plan event bridge spawns bridge for a session, relays each event's text into the session, and stores the delivered ids. plan_connect only connects the session; it mints no ticket.

CLAUDE_CODE_SESSION_ID=<session> npx -y @thehammer/danx-dashboard-mcp@<version> bridge [--resume-ids <id>,<id>,...]
  • Credential — the session's own, never the ambient one (DX-2862). The session id is the only thing bridge takes from its environment; --resume-ids is the only argument and holds no secret. Everything else comes from the connection record this session's own danx-dashboard MCP server wrote on plan_connect (src/session-connection.ts): the dashboard URL, the credential's SOURCE, and its FINGERPRINT. bridge resolves that source through the same resolver the server used and refuses to run unless the result fingerprints identically — credential_mismatch. Before that fix it used whatever DANXBOT_DISPATCH_TOKEN the session's environment held, and on a machine where that differed from the server's credential the dashboard admitted the stream and dropped every event, with nothing logged anywhere.
  • No client-side reach check (DX-2920). bridge used to prove board reach itself, before ever opening the stream (GET /api/plans/mine?fields=cards + a per-board GET /api/issues/:id/problems). That check is DELETED — the dashboard (DX-2863) now enforces the identical requirement server-side, and a second client-side copy of the same check was a duplicate source of truth, not a safety net: it refuses a mint (403 issuer_cannot_read_plan_boards), refuses stream admission (the SAME 403), and ends a live stream (event: end, {"reason":"scope_narrowed","boards":[…]}) the instant it stops being true. bridge only MAPS those refusals now — see "Stopping" below — it never independently verifies anything before streaming. Once a ticket is minted, bridge emits, once, {"type":"ready"} — no boards field, since reach is no longer proven client-side.
  • Ticket. bridge mints the session's listener ticket (POST /api/plan-sessions/me/stream-ticket, 10 s timeout) and keeps it in the process. A ticket authorizes reading that one session's event stream and nothing else, and is only issued while the session is connected to a plan. Minting a new one ends the previous listener.
  • Output — JSON Lines. One {"type":"event","id":<id|null>,"text":"…"} per event on the connected plan's cards, where text is [DX-8 "Title" repo:board] newms87 answered "Which rollout order?": chose "Pause E2E" — note: "only this week", … answered "…": "<free-form answer>", … commented: "…", … opened a problem: "…", … blocked the card: "…", or … unblocked the card. An event it cannot read still produces one, with a could not read event text. Nothing for keep-alives, reconnects or re-mints. The session's own writes are never echoed back to it.
  • Stopping. Last, one {"type":"stopped","reason":"…","detail":"…","fix":"…"} and exit, only on a terminal outcome. fix is the remedy shown to the session (STOP_FIXES in src/bridge.ts — never a log-only hint), so a session that hits a stop it cannot otherwise see still knows what to do about it. Terminal reasons: no_connection_record / credential_unavailable / credential_mismatch (the start-time checks above), not_connected (a 409 session_not_connected at mint OR at stream admission, or a live end("not_connected") — all three mapped identically), unauthorized (401/403 for a reason OTHER than an unreadable board), mint_refused (any other non-transient mint refusal), mint_bad_response, board_unreadable (a 403 issuer_cannot_read_plan_boards at mint OR at stream admission, naming the unreadable boards — DX-2920), scope_narrowed (a LIVE stream's end("scope_narrowed", boards) — the server's end() REQUIRES boards for this reason at the type level, bridge parses them off the wire and names them in detail/fix, never a generic message), bad_end_payload (a scope_narrowed end whose boards array was missing or malformed — a protocol error surfaced loudly, never silently downgraded to a boards-less scope_narrowed), superseded / replaced (exit 0 — a newer or replacing connection already serves this session, so fix says no action is needed), revoked, or refused (two freshly minted tickets refused in a row with no more specific classification). A transient mint failure (network, timeout, 408, 429, 5xx) backs off and retries; a lapsed ticket lease re-mints.
  • Reconnect and resume. A read-idle timeout (three missed keep-alives) turns a silently dead connection into a drop. Capped exponential backoff (1s → 30s) that resets only after a healthy connection, with Last-Event-ID, so a dashboard restart replays what was missed and nothing is emitted twice. --resume-ids carries the same guarantee across a process restart: the ids already delivered seed the duplicate guard, and the highest is the first Last-Event-ID. The dashboard floors that replay at the later of the session's first ticket and when it joined its current plan, so a restart loses nothing and a plan move replays nothing from before the move.

Build + test

cd packages/danx-dashboard-mcp
npm install
npm test
npm run build   # → dist/index.js (executable)

Publishing

Out of scope for this package's own code — the make publish-danx-dashboard-mcp target in the danxbot root Makefile owns version bump + npm publish + propagation wait. Standing operator authorization per ~/.claude/skills/thehammer-publish/.