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

planb-mcp

v0.0.0-alpha.0

Published

Local stdio MCP server that opens a browser form so the user can answer questions, via URL-mode elicitation.

Readme

A local stdio MCP server for planning sessions. Its centerpiece is ask_user, which opens a modern browser form for the user to answer one or more questions and returns their answers to the calling agent. The form is laid out as a tab panel — an optional Markdown intro tab plus author-defined sections — and each option can carry its own Markdown illustration that renders in a side panel as the user moves through the choices.

Beyond asking, planb-mcp persists the planning session. A session ties together (a) one or more ask_user questioning rounds and (b) one or more saved plan versions under a single session id, so the whole arc — what was asked, what the user answered, and how the plan evolved — can be replayed later by a future retrospective app. Three tools cooperate around the session id: you call init_planb_session once to mint it, then pass that id to every ask_user and save_plan call.

The browser is opened in one of two ways, chosen automatically per client:

  • URL-mode elicitation (MCP spec 2025-11-25, SEP-1036) when the client advertises capabilities.elicitation.url (e.g. Claude Desktop / claude.ai/code).
  • Direct open otherwise: since this server runs locally, it opens your default browser itself. This covers the Claude Code CLI, which advertises a bare elicitation: {} with no url sub-capability.

Either way, the user's answers do not flow back through the agent — they are submitted directly to a local HTTP callback server that this MCP server runs on an ephemeral 127.0.0.1 port.

⚠️ Non-sensitive input only. This UI is for preferences, creative answers, and choices. Do not collect passwords, API keys, tokens, or payment data.

Requirements

  • Node.js 18+
  • Any MCP client. Tested with the Claude Code CLI 2.1.159 (uses the direct-open path) and works with URL-elicitation-capable clients (Claude Desktop / claude.ai/code).

Client elicitation support

| Client | advertises elicitation.url? | How the browser opens | |--------|-------------------------------|------------------------| | Claude Code CLI (2.1.x) | No (sends bare elicitation: {}) | Server opens it directly | | Claude Desktop / claude.ai/code | Yes | Via URL-mode elicitation |

Install

npm install

Run

The package ships a single executable, planb-mcp, that boots the MCP stdio server (it loads the TypeScript through tsx in-process — no build step). It writes nothing to stdout (reserved for JSON-RPC); all logs go to stderr. You normally don't run it by hand — Claude Code launches it once registered (below). Equivalent ways to start it:

planb-mcp          # if globally installed / linked (see "Make `planb-mcp` global")
npx planb-mcp      # after publishing, or from a global link
npx .                   # from this project directory, no install needed
npm start               # from this project directory
npm run serve           # legacy: tsx projects/mcp/server.ts directly

Make planb-mcp global

npx planb-mcp by name resolves either from the npm registry (after publishing) or from a global link. This package is private: true, so the no-publish route is a global link/install from the project directory:

npm link            # symlinks `planb-mcp` onto your PATH (reversible: npm unlink -g planb-mcp)
# or:
npm install -g .    # installs it globally from source

After that, planb-mcp and npx planb-mcp work from anywhere. (To publish to the registry instead, remove "private": true from package.json and run npm publish.)

Register in Claude Code (local stdio)

The three tools (init_planb_session, ask_user, save_plan) should appear in the tool list after registering (restart Claude Code or reconnect MCP servers; verify with claude mcp list).

Option A — global command (simplest, after npm link/global install)

claude mcp add planb-mcp -- planb-mcp

Option B — absolute path to the bin (no global install needed)

The bin is a self-contained executable (shebang + tsx loader), and the server resolves its public/ assets relative to its own file, so this works regardless of launch directory. Replace /ABS/PATH with this project's absolute location (e.g. /ABS/PATH/TO/planb-mcp).

claude mcp add planb-mcp -- /ABS/PATH/bin/planb-mcp.mjs

Or as JSON in ~/.claude.json (user scope) or a project-local .mcp.json:

{
  "mcpServers": {
    "planb-mcp": {
      "command": "/ABS/PATH/bin/planb-mcp.mjs"
    }
  }
}

Tools

planb-mcp exposes three tools. The session id minted by init_planb_session is the thread that ties a questioning-and-planning arc together; pass it to every ask_user and save_plan call.

| Tool | Args | Returns | |------|------|---------| | init_planb_session | title?: string, intent?: string (both optional) | Text sessionId: <uuid> followed by a short usage note. Creates the session on disk. No browser. | | ask_user | sessionId: string (UUID, required), title, introTitle?, intro?, questions[] | Text holding the answers JSON { [questionId]: answerValue } (or a cancel/timeout message). Opens the browser form. Persists the question payload and the outcome under the session. | | save_plan | sessionId: string (UUID, required), plan: string (Markdown, required), title?: string | Text Saved plan v<N> for session <id> plus the absolute file path. Persists the plan as a new version each call (history kept). No browser. |

Usage flow

  1. Call init_planb_session once — note its sessionId.
  2. Pass that sessionId to every ask_user round (gather requirements) and every save_plan (snapshot the plan).
  3. Each save_plan writes a new version, so the full plan history is preserved under the session.

If ask_user or save_plan is given a sessionId that was never created, it returns a message telling the agent to call init_planb_session first.

The ask_user tool

| | | |---|---| | Name | ask_user | | Title | Ask the user (browser UI) | | Description | Opens a browser form so the user can answer one or more questions, and returns their answers. |

Requires a sessionId. Every ask_user call now takes the UUID returned by init_planb_session (in addition to the fields below). The id is used only to persist the ask under the session — the browser form never sees it.

Input schema

{
  sessionId: string,    // UUID from init_planb_session (required)
  title: string,        // heading at the top of the form (plain text)
  introTitle?: string,  // optional concise title for the intro tab (default "Overview")
  intro?: string,       // optional intro — GitHub-flavored Markdown (see below)
  questions: Question[] // 1..20 items
}

Tabs & navigation

The form is a tab panel. If intro is provided it becomes the first tab. Questions are then grouped into tabs by their optional tab field (questions that share a tab value land in the same tab; tab order follows first appearance; untagged questions fall into a single "Questions" tab). When there is only one tab, the tab strip is hidden.

  • Left / Right arrows switch tabs (the tab actually changes — automatic activation), as does clicking a tab; Home / End jump to the first / last.
  • Up / Down arrows move between a question's options; Enter / Space select (single) or toggle (multi).
  • Text fields and sliders keep their native arrow behavior.
  • A footer carries ‹ Back / Next › and, on the final tab, Submit (enabled once every required question is answered). Tabs that still contain an unanswered required question show a small dot.

Markdown in intro and option illustrations

intro is rendered as GitHub-flavored Markdown in the browser form: headings, lists, tables, links, blockquotes, syntax-highlighted code blocks (js, python, ts, tsx via highlight.js), and diagrams. Diagrams can be either Mermaid (mermaid) for flowcharts/sequences/graphs, or a plain-text/ASCII sketch in a text block when a quick hand-drawn layout reads more clearly — the choice is left to whoever authors the intro. The markdown HTML is sanitized with DOMPurify before rendering. Rendered Mermaid diagrams are clickable — they open in a large lightbox modal (close with the × button, a backdrop click, or Esc).

The same renderer powers per-option illustrations: for single/multi questions, an option may be { value, markdown } instead of a plain string. When any option in a tab has markdown, that tab gains a side panel that shows the focused option's illustration, updating live as the user moves between options. Tabs with no option illustrations stay single-column.

The renderer (marked, mermaid, highlight.js, dompurify) is loaded from a CDN via an import map in public/index.html. If the CDN is unreachable, the markdown gracefully falls back to plain text and the rest of the form still works. title and question labels remain plain text.

Question kinds

| kind | UI control | Answer value type | |------------|-----------------------|-------------------| | text | single-line input | string | | longtext | multi-line textarea | string | | single | radio group | string | | multi | checkbox group | string[] | | scale | range slider | number |

type Option = string | { value: string; markdown?: string };

type Question = { id: string; label: string; required?: boolean; tab?: string } & (
  | { kind: "text";     placeholder?: string }
  | { kind: "longtext"; placeholder?: string }
  | { kind: "single";   options: Option[] }
  | { kind: "multi";    options: Option[] }
  | { kind: "scale";    min: number; max: number; step?: number }
);
  • id is unique within the spec and is the key in the returned answers object.
  • required is enforced client-side before submit is allowed.
  • tab (optional) groups the question into a named section/tab. Keep it concise.
  • For single/multi, each option is a plain string or { value, markdown }. The answer is always the option's value; markdown (optional) renders in the tab's side panel when the option is focused. Option values must be unique within a question.

Return value

Text content holding a JSON string of { [questionId]: answerValue }.

Other outcomes

  • Unknown sessionId (never created) → a message telling the agent to call init_planb_session first; no browser opens.
  • Declined / cancelled elicitation (URL-elicitation path only) → "User cancelled."
  • No submission within 24 hours → a timeout message; the process stays healthy. (Override the window with the ASK_ANSWER_TIMEOUT_MS env var.)

Environment variables

  • ASK_ANSWER_TIMEOUT_MS — answer timeout in ms (default 86400000, i.e. 24h).
  • ASK_NO_OPEN=1 — on the direct-open path, log the URL instead of launching a browser (for tests / headless environments).
  • PLANB_MCP_DATA_DIR — override the storage root (default ~/.planb-mcp); see Storage.

Example invocation (covers all five kinds, tabs, and an option illustration)

{
  "name": "ask_user",
  "arguments": {
    "title": "Project kickoff preferences",
    "introTitle": "Kickoff",
    "intro": "A few quick questions to set up your workspace.",
    "questions": [
      { "id": "project_name", "kind": "text", "label": "Project name", "placeholder": "e.g. Aurora", "required": true, "tab": "Workspace" },
      { "id": "summary", "kind": "longtext", "label": "One-paragraph summary", "placeholder": "What are we building?", "tab": "Workspace" },
      { "id": "language", "kind": "single", "label": "Primary language", "required": true, "tab": "Workspace",
        "options": [
          { "value": "TypeScript", "markdown": "### TypeScript\n\nFull-stack web with strong typing." },
          "Go", "Rust", "Python"
        ] },
      { "id": "integrations", "kind": "multi", "label": "Integrations to enable", "options": ["GitHub", "Slack", "Linear", "PagerDuty"], "tab": "Integrations" },
      { "id": "priority", "kind": "scale", "label": "Priority (1 = low, 5 = high)", "min": 1, "max": 5, "step": 1, "required": true, "tab": "Priority" }
    ]
  }
}

A possible returned result:

{
  "project_name": "Aurora",
  "summary": "A realtime collaboration tool.",
  "language": "TypeScript",
  "integrations": ["GitHub", "Slack"],
  "priority": 4
}

The init_planb_session tool

| | | |---|---| | Name | init_planb_session | | Description | Mint a session id and create the session on disk. Call this once at the start of a planning arc. |

Input schema

{
  title?: string,   // optional short title for the session
  intent?: string,  // optional one-line description of what you're planning
}

Return value

Text content whose first line is sessionId: <uuid>, followed by a short note to pass that id to subsequent ask_user / save_plan calls. Creating the session writes <root>/sessions/<id>/session.json (see Storage).

The save_plan tool

| | | |---|---| | Name | save_plan | | Description | Persist a plan (Markdown) under a session as a new version. History is kept. No browser. |

Input schema

{
  sessionId: string,  // UUID from init_planb_session (required)
  plan: string,       // the full plan as a Markdown string (required)
  title?: string,     // optional title for this plan version
}

Return value

Text content:

Saved plan v<N> for session <id>
path: <absolute path to plans/v<N>.md>

Each call allocates the next version (v1, v2, …) — earlier versions are kept on disk so the plan's evolution can be replayed. An unknown sessionId returns a message telling the agent to call init_planb_session first.

Storage

planb-mcp persists each session under a data root — $PLANB_MCP_DATA_DIR if set, otherwise ~/.planb-mcp:

~/.planb-mcp/
  sessions/
    <sessionId>/
      session.json              # manifest: title?, intent?, timestamps,
                                #   latestPlanVersion, planCount, askCount,
                                #   and asks[]/plans[] index entries
      asks/
        <utcTs>-<askId>.json    # one per ask_user call: the full question
                                #   spec + status (pending|answered|timeout|
                                #   declined|error) + answers/outcome
      plans/
        v1.md   v1.json         # plan markdown + metadata sidecar
        v2.md   v2.json         #   (version, title?, createdAt, file, bytes)
        ...
  • The session id is the primary key tying every ask and plan version together, so the bundled retrospective viewer (planb-retro, see below) can read this layout and replay the whole planning arc.
  • Set PLANB_MCP_DATA_DIR to relocate the root (handy for tests — point it at a temp dir).
  • Persistence is best-effort: a disk failure is logged to stderr but never blocks an answer or crashes the server.

Retrospective viewer (planb-retro)

The package ships a second executable, planb-retro, a small read-only web app that browses everything planb-mcp has persisted. It reads the same data root ($PLANB_MCP_DATA_DIR or ~/.planb-mcp) and renders, per session, the questions that were asked, the answers the user gave, and every saved plan version — so you can replay how a planning arc unfolded.

planb-retro          # if globally installed / linked
npx planb-retro      # after publishing, or from a global link
npm run retro        # from this project directory

It starts an Express server on http://127.0.0.1:4317 by default (override with the PORT env var). It only reads the data root — it never modifies a session or opens the form.

The /planb skill

A user-invoked /planb skill (skills/planb/SKILL.md) drives a full planning effort through the three tools above, so the whole arc is tied together under one session id and persisted for later review. It requires the planb-mcp MCP server to be connected (see Register in Claude Code).

How it works

  1. On the first ask_user call, a lazy express callback server binds an ephemeral port on 127.0.0.1 (port 0 → OS-assigned). One instance per process, reused thereafter.
  2. The tool generates a sid, stores the question spec under it, registers a pending resolver, and builds http://127.0.0.1:<port>/ask?sid=<sid>.
  3. It opens that URL in the browser:
    • if the client advertises elicitation.url, via a URL-mode elicitation (the client opens it and the server later sends notifications/elicitation/complete);
    • otherwise the server opens the default browser directly.
  4. The browser page fetches GET /spec?sid=..., renders the questions, enforces required, and on submit POSTs { sid, answers } to /submit.
  5. /submit resolves the pending promise and the tool returns the answers as JSON. The answer timeout (24h by default) guards against no submission.

File structure

planb-mcp/
  package.json            # "type": "module"; "bin": planb-mcp + planb-retro; scripts
  tsconfig.json
  LICENSE
  bin/
    planb-mcp.mjs         # MCP server entry point (loads projects/mcp/server.ts via tsx)
    planb-retro.mjs       # retrospective viewer entry (loads projects/plans-retro/src/server.ts)
  skills/
    planb/SKILL.md        # the user-invoked /planb planning workflow
  projects/
    mcp/
      server.ts           # MCP stdio server: init_planb_session, ask_user, save_plan + lazy callback server
      tool-schemas.ts     # zod input shapes for the three tools (sessionId, save_plan, ...)
    store/
      index.ts            # public entry: re-exports the session store API + types
      store.ts            # session store API (createSession, appendPendingAsk, appendPlanVersion, ...)
      types.ts            # on-disk JSON types (SessionRecord, AskRecord, PlanMeta, ...)
      paths.ts            # path helpers for the sessions/<id>/ layout
      fs-utils.ts         # atomic JSON/text writes
      lock.ts             # per-session write lock
    ask-user-app/
      src/
        callback-server.ts  # ephemeral-port express server: /ask, /spec, /submit
        open-browser.ts     # opens the default browser (MCP fallback + open-app dev script)
        questions.ts        # Question type + tool input schema (zod)
      public/
        index.html        # UI shell: import map (React, htm, Tailwind, md libs) + mount node
        app.js            # entry: loads Tailwind, mounts the React app into #root
        components.js     # React form: App, tab strip/panels, question fields, footer, state screens
        intro.js          # async Markdown renderer (intro tab + option panel) + diagram lightbox (React)
        markdown.js       # marked/DOMPurify/highlight.js/mermaid glue
        validation.js     # required-field logic (pure)
        theme.js          # light/dark + palette controls, persisted to localStorage
        styles.css        # theme tokens + markdown/diagram/loading styles (Tailwind-uncovered)
        logo.svg          # shared brand mark (CSS-masked to the active accent)
        favicon.svg       # theme-aware favicon variant of the logo
      examples/           # sample AskUserInput configs for the open-app dev launcher
        light.json
        medium.json
        heavy.mjs
    plans-retro/          # retrospective viewer (planb-retro): read-only web app
      src/
        server.ts         # Express SSR server: lists sessions, renders asks + plan versions
      views/              # EJS templates (index, session, questionnaire, plan, partials)
      public/             # retro.css + client JS (serves ask-user-app/public under /ui)
  scripts/
    open-app.ts           # dev launcher: render the UI from an examples/ config (npm run open-app)
    planb-mcp-smoke.ts       # end-to-end smoke test: drives the real server over stdio (npx tsx scripts/planb-mcp-smoke.ts)

Security notes

  • Callback server binds 127.0.0.1 only, on an ephemeral port.
  • No auth beyond the unguessable sid (session maps are in-memory only).
  • The UI sets no cookies and stores no answers locally — the only thing it keeps in localStorage is a non-sensitive theme/palette preference. It is built with React + htm + Tailwind, loaded as native ES modules from a CDN (esm.sh) via the import map in index.html — same mechanism as the Markdown/diagram renderer, and still with no bundler or build step. Answers are POSTed only to the local callback server.
  • stdout is never written to — a single stray write would corrupt the JSON-RPC stream.

License

Released under the MIT License — © 2026 Ramy Ben Aroya.