ask-user-rich
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@ask-user-richgrill me about my product roadmap"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
ask-user-rich
A stdio MCP server that gives Claude Code a richer way to ask the user than the built-in
AskUserQuestion, which is limited to 1–4 questions, 2–4 options and 12-character headers. It is built
for long structured interviews, or "grilling rounds". Claude sends the whole interview in one call, the
server serves it as a local web form, and the call blocks until the user submits.
Requires Node.js 20 or newer. Works with Claude Code and any other MCP client that supports stdio servers.
Quick start
Register it with Claude Code at user scope, so it is available in every project:
claude mcp add-json --scope user ask-user-rich \
'{"type":"stdio","command":"npx","args":["-y","github:PedroRF-tension/ask-user-rich"],"timeout":14400000}'
claude mcp get ask-user-rich # expect: Status: ✔ ConnectedThen ask Claude to "grill me with ask_user_rich about …", or let it choose the tool when a round of
questions outgrows AskUserQuestion. See Install for a local clone instead of npx.
Related MCP server: codex-mcp
Tools
ask_user_rich
The input has:
title;an optional
intro(markdown);questions, with no upper bound;delivery, one ofbrowser(default),linkorelicitation.
Each question has:
id;header, the question itself, with no length cap;body(markdown);options, with no upper bound. Each option has anid, alabel, a markdowndescriptionand an optional markdownpreview, such as a fenced code block. An emptyoptionslist makes the question free-text only.kind:choice(default) orrank. A rank question has the user put every option in order of priority, by dragging or from the keyboard. It needs at least 2 options, takes nomultiSelect, and has no Other field.recommended: an option id, or an array of ids whenmultiSelectis on. For a rank question it is the recommended order: an array holding every option id exactly once.rationale(markdown);multiSelect;allowOther: free text, defaulttrue;dependsOn: ids of earlier questions. It only affects display, as a "Builds on #n" link.
The form is a stepper that shows one question per screen:
It opens on an intro screen (only when
introis set), then shows one screen per question, then a review screen. The review screen lists every answer and note; click a row to jump back to that question. It also holds the general-notes box and the submit button. If some questions are still unanswered, the first submit asks for confirmation.A numbered rail in the header shows each question's status (answered, deferred, needs info, open) and the current screen. Click a number to jump to that question. A progress bar tracks how many questions are resolved.
Screens slide in from the direction you move, and the slide is skipped when
prefers-reduced-motionis set.Each question shows its recommendation and rationale, single or multi select, Other text, a note, and an Answer / Defer / Need more info switch. "Builds on" links jump to the earlier question.
A rank question shows numbered rows. You can drag them with the mouse or move them from the keyboard (see the table), and the other rows slide aside. Nothing is recorded until you move a row or press "Keep this order" (
C), so Claude never mistakes the default order for a deliberate ranking.Show all (
V, or the header button) stacks every screen on one page. The rail then scrolls to a question instead of switching screens, and it tracks the question you are reading as you scroll. The choice is remembered.Scrolling follows the keyboard. When focus moves by keyboard (arrows, Tab,
N,O, …), the page scrolls smoothly so the focused item stays in the middle half of the space between the header and the footer. It no longer waits until the item is off screen. A tall option, such as one with a big preview, is pinned near the top instead. Mouse clicks never scroll the page.Live follow-ups. When Claude calls
append_questions, the open form picks up the new questions within about 2.5 s: new rail chips carry a dot, a notice shows Claude's note, andGjumps to the first new question. If a submit races an append, the server refuses it (409). The form then pulls in the new questions and asks you to look at them before submitting again, so no question is lost unseen.
Everything can be done from the keyboard (press ? in the form for the same table):
Keys | Action |
| Previous / next screen |
| Next screen. On a single-choice question with nothing picked yet, it first picks the focused option. On the review screen it submits. |
| Next screen or submit, even while typing in a text field |
| Go to the first screen / the review screen |
| Move between options. The option list is a single Tab stop. |
| Pick or unpick the focused option |
| Pick an option by its number |
| Other: select it and focus its text field |
| Take the recommendation |
| Focus the note |
| Toggle Defer / Need more info. |
| Clear the answer |
| On the review screen: accept every recommendation that is still unanswered |
| Show all questions on one page / back to one per screen |
| Go to the follow-up questions Claude just added |
Rank: | Pick up the focused row, move it, drop it. |
Rank: | Move the focused row directly |
Rank: | Send the focused row to that position |
Rank: | Keep the order as shown / apply the recommended order / reset |
| Leave a text field so the single-key shortcuts work again |
| Options → Other → answer mode → note → Back / Next |
The form works in light and dark mode and follows the OS setting unless the Theme button forces one. Drafts survive a reload because they are stored in the browser's localStorage, along with the screen you were on. If Claude stops waiting, the page says so and the answers can still be submitted.
The result has:
structuredContent:{ summary, interviewId, title, status, via, submittedAt, durationSeconds, counts, generalNotes, answers[] }. Eachanswers[]entry is{ id, header, status, selected, selectedLabels, other, ranked, rankedLabels, notes, followedRecommendation }, wherestatusisanswered,deferred,needs-infoorunanswered.rankedandrankedLabelsgive the order for an answered rank question, and arenullotherwise.For a rank question,
followedRecommendationmeans the order matches the recommendation exactly. The summary line readsA > B > C.
content: the same summary as text, followed by the JSON.
Claude Code 2.1.280 gave the model only structuredContent and dropped the text blocks when both were
present. That is why the summary is also carried inside structuredContent.
The server rejects input it cannot use and lists every problem it finds, each with its fix where one is known:
Non-ASCII ids. Ids must match
[A-Za-z0-9_.:-]. The error names the bad value and suggests an ASCII slug:"seção-tabs" is not ASCII-safe … use "secao-tabs" instead. Labels, headers and bodies take any Unicode.Bad
recommended. Arecommendedvalue that is not an option id is rejected. When it matches a label, the error names the id to use instead. Several recommendations on a single-select question are also rejected.Bad
dependsOn. AdependsOnthat names a later question is rejected with "move X before Y". One that names an unknown id, or the question itself, is rejected too.Bad rank questions. A rank question is rejected if it has fewer than 2 options or has
multiSelect. It is also rejected if itsrecommendedis not a full order: the error names the missing, repeated or unknown ids.Duplicate ids.
A question that cannot be answered at all: no options and
allowOther: false.
The MCP instructions and the tool description list these same mistakes, so the model sees them before
the first call, not after the first rejection.
append_questions
Takes { sessionId, questions, note? } and adds follow-up questions to an interview whose form is still
open. It returns at once with { status: "appended", sessionId, appended, total, version, url }.
Use it when ask_user_rich returned "awaiting", or while an ask_user_rich or await_user_answers call
is still waiting in the background. The user answers everything in one submit, and the answers arrive
through the call that is already waiting (or through await_user_answers).
The questions follow the same rules as in ask_user_rich:
ids must be unique across the whole interview;
dependsOnmay name any existing question, or an earlier question in the same call.
Error numbering starts at questions[0], the first appended question. Once the user has submitted,
the call fails and tells the model to ask a new interview instead.
HTTP contract behind it:
Every append increments the session's
version.GET /api/s/:tokenand/statereport the current version, andGET /api/s/:tokenalso returnsnote.A submit carrying an outdated
specVersiongets a 409 with the currentversion.A submit that leaves
specVersionout is still accepted.
await_user_answers
Takes a { sessionId } and waits for an interview that is still pending. An interview is left pending
by delivery: "link", by a failed browser open, or by an earlier call that was cancelled. If the user
has already submitted, it returns at once.
Waiting, progress and timeouts
While it waits, the server sends a
notifications/progressevery 15 s (ASK_USER_RICH_PROGRESS_MS). Claude Code resets its idle timer on these (docs: "A tool call to an MCP server that sends no response and no progress notification for the idle window aborts"). Claude Code does send aprogressToken; the log showsprogressToken=present.The server is registered with
"timeout": 14400000, which is 4 hours. It is a hard wall-clock limit per call. Because it is at least 1000 ms, it also acts as a floor for the idle timeout.In an interactive session, a call still running after 2 minutes becomes a background task; the answers come back as a task notification.
claude -pdoes not background calls.If a call is cancelled or times out, it stops waiting cleanly, but the form stays open. Submitted answers are kept, and
await_user_answerscan still collect them.
Delivery modes
browseropens the form withopenon macOS,cmd /c starton Windows, andxdg-openon Linux.On WSL with interop enabled, it tries
wslview, thenexplorer.exe, thencmd.exe /c startandpowershell.exe Start-Process, stopping at the first that works. It skips any that aren't installed.cmd.exeandpowershell.exerun with/mnt/cas their working directory. The URL is always passed as an argument, never built into a shell string.cmd.exeis also skipped for URLs with characters it would reinterpret.On WSL with interop disabled, no Windows opener can run, so none is tried. It uses
xdg-openonly if all of these hold:a Linux GUI is available (
DISPLAYorWAYLAND_DISPLAYis set);xdg-openandxdg-mimeare installed;xdg-mimenames a real https handler that isn'twslview.
This is strict on purpose. Reporting "opened" for a browser that never appeared would make the tool wait on a form nobody can see.
If every opener fails, the tool returns the URL and a
sessionIdright away instead of blocking. The reason lists what was tried, for example:WSL interop is disabled (…), so Windows openers (explorer.exe, cmd.exe, powershell.exe) cannot run; xdg-open is not installed; re-enable interop …. Claude shows the link and callsawait_user_answers.
linknever opens a browser and always returns the URL straight away.elicitationuses the client's native MCP form dialog, when the client advertises form elicitation. It suits small, flat interviews: one field per question, plus an "Other" field. It has no notes, no defer and no previews. If the client can't elicit, or the interview has a rank question, it falls back tobrowser. This mode is covered by tests with an SDK client only; it has not been tried in Claude Code's own dialog.
WSL: when the browser never opens
If the tool always returns a link on WSL, interop is probably disabled: /proc/sys/fs/binfmt_misc has no
WSLInterop entry, so no .exe can run from Linux. A known cause is systemd=true in /etc/wsl.conf,
which can drop the binfmt registration. A commonly used fix is:
sudo sh -c 'echo ":WSLInterop:M::MZ::/init:PF" > /usr/lib/binfmt.d/WSLInterop.conf'
sudo systemctl restart systemd-binfmt # or: wsl.exe --shutdown from Windows, then reopenAfter the fix, check with ls /proc/sys/fs/binfmt_misc | grep WSLInterop and
explorer.exe https://example.com. Until then, the link fallback still works: open the URL by hand.
Install, reinstall, uninstall
claude mcp add has no timeout flag, so the server is registered through add-json with the documented
timeout field (4 hours; see Waiting, progress and timeouts).
Option A: npx straight from GitHub (no clone needed; npx caches the install):
claude mcp add-json --scope user ask-user-rich \
'{"type":"stdio","command":"npx","args":["-y","github:PedroRF-tension/ask-user-rich"],"timeout":14400000}'Option B: a local clone (pinned to what you checked out, and easy to hack on):
git clone https://github.com/PedroRF-tension/ask-user-rich.git ~/.claude/mcp-servers/ask-user-rich
cd ~/.claude/mcp-servers/ask-user-rich && npm ci && npm test
claude mcp add-json --scope user ask-user-rich \
"{\"type\":\"stdio\",\"command\":\"$HOME/.claude/mcp-servers/ask-user-rich/run.sh\",\"args\":[],\"timeout\":14400000}"run.sh uses node from PATH. If PATH has none, it falls back to the newest ~/.nvm Node. You can
also pin one with ASK_USER_RICH_NODE. To update a clone: git pull && npm ci.
Check it with claude mcp get ask-user-rich (expect Status: ✔ Connected, Timeout: 14400000ms).
To reinstall after moving the directory or changing the timeout, or to uninstall:
claude mcp remove --scope user ask-user-rich
# then run the add-json command again, or delete the clone to uninstallOther MCP clients: run npx -y github:PedroRF-tension/ask-user-rich (or run.sh from a clone) as a
stdio server, and give tool calls a long timeout, since a call waits for the user.
Running it by hand
npm test # 39 tests: an SDK client driving the server over stdio, plus the opener plan
npm run dev # asks examples/demo-interview.json and prints the result
node scripts/dev.mjs my.json --link # print the URL, then wait (use when no browser can be opened)
node scripts/dev.mjs --no-open # block without opening; the URL is in the progress lines
./run.sh # raw stdio server (what Claude Code launches); Ctrl-D to stopConfiguration (environment variables)
Variable | Default | Effect |
| on |
|
| platform opener | Command run with the URL as its only argument |
|
| Fixed port for the form server |
|
| Bind address |
|
| Hostname used in the URL |
|
| Progress notification interval |
|
| Log and answer archive location |
Set these with -e/env on the MCP entry, or in the environment Claude Code starts with. The server
inherits Claude Code's environment.
Logs
logs/server.logrecords every call, session, browser-open result, progress tick, page view (with its user agent) and submit. The same lines go to stderr, which Claude Code captures inclaude --debugoutput.logs/answers/<timestamp>-<id>.jsonarchives every submitted interview, so answers survive even when the tool call that asked for them is gone.
Security
The server listens on loopback only.
Each interview URL carries a random 144-bit token.
Requests whose
Hostheader is not loopback get a 403, which blocks DNS rebinding.Submits must be
application/json. That forces a CORS preflight, which the server never answers, so other origins cannot post blind.Markdown is rendered with
markedand sanitized with DOMPurify. Both are served from localnode_modules, so no CDN is used.
Steering Claude toward it (optional, not installed anywhere)
The server already sends MCP instructions that recommend it over AskUserQuestion for large
interviews. To make that stronger, you can add this to ~/.claude/CLAUDE.md or to a skill:
## Asking me questions
When you need my input on more than 4 questions, more than 4 options, long question text, code or layout
previews, or a recommendation you want to argue for, use the `ask_user_rich` tool (MCP server
ask-user-rich) instead of AskUserQuestion. Put every question of the round in one call, give each one a
recommended option with a rationale, and treat `deferred` / `needs-info` answers as open, not as consent.
If it returns a link instead of answers, show me the link and call `await_user_answers`.License
MIT. See LICENSE.
This server cannot be deployed
Maintenance
Related MCP Connectors
Live SEO workflow tools for Claude Code, Codex, and AI agents.
I do everything related to interviews and structured Q&A
AI-native form builder: create, publish & read responses from Claude, ChatGPT & MCP.
Build, clone & publish websites by chatting with Claude. Live in seconds, custom domains + SSL.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceTurns Claude Code into an AI Agent Studio with a guided menu and 11 tools to design, create, and manage multi-agent projects without coding.1MIT
- AlicenseAqualityAmaintenanceBridges Claude Code and OpenAI Codex CLI for an interactive plan-execute-review workflow, enabling Claude to interview, design, and review while Codex implements code changes.7291 npm3MIT
- AlicenseAqualityDmaintenanceTurns Claude into a structured interviewer with a collective evolution system that gets smarter with each conversation.423 npmMIT
- AlicenseNot gradedqualityBmaintenanceLets Claude Code drive your own Chrome browser with human-like clicks, drags, and typing. It uses your real sessions and logins, and pauses for your confirmation on anything irreversible.8MIT