Authoring walkthroughs
The walkthrough-plugin renders styled popups inside IntelliJ next to specific lines of code (or to one side of an IDEA diff viewer), lets the user step through items with Previous/Next, and lets them type follow-up questions that you answer by inserting child steps.
The MCP tools form one stateful protocol. Skipping the question-loop leaves the popup waiting for input that never arrives.
Tools
All tools live on the idea MCP server. Fully qualified names:
mcp__idea__show_walkthrough_items— show a file walkthrough; create or replace its saved history entrymcp__idea__show_diff_walkthrough_items— show a diff walkthrough; create or replace its saved history entrymcp__idea__await_walkthrough_question— blocks until the user asks a question or dismissesmcp__idea__insert_walkthrough_tangents— splices answer steps as children of a step
If these tools are not available, do not pretend to create an inline walkthrough. Tell the user that the IntelliJ walkthrough plugin and its idea MCP server are required, then offer a normal text walkthrough only if they want one.
Choose File vs Diff Mode
Before preparing items, choose exactly one mode for the whole walkthrough. Sessions are single-kind; you cannot mix file and diff items in one call or one session.
Use file mode for:
- explaining how existing code works
- architecture or onboarding tours
- tracing runtime behavior
- debugging a current implementation without focusing on a change set
Use diff mode for:
- PR review
- commit, branch, or patch review
- "what changed?", "review my changes", or "explain this diff"
- risk analysis, regression analysis, or test recommendations for a change set
If the user asks both to review changes and explain surrounding architecture, start with a diff walkthrough and use follow-up tangent steps for extra context, or ask whether they also want a separate file walkthrough afterwards.
Required protocol
show_walkthrough_items(description, items, historyId?) # file mode
OR
show_diff_walkthrough_items(description, payload, historyId?) # diff mode
remember walkthroughId and the saved history ID from the response
loop:
await_walkthrough_question(walkthroughId) → "dismissed" | (parentLabel, question)
if dismissed: stop
if the user requests a presentation revision:
show the full revision using the saved-presentation rules below
continue with the new walkthroughId
answer = build items addressing the question
insert_walkthrough_tangents(walkthroughId, parentLabel, answer)
You must enter the await loop immediately after the show tool returns. Do not summarize, do not stop, do not call other tools first. The popup's question UI depends on you waiting. Keep looping until await_walkthrough_question returns the literal string dismissed.
If a walkthrough tool returns an error such as No active project, No active editor, No diff walkthrough items to show, or Unknown walkthroughId, tell the user what failed and stop the loop. Do not retry blindly.
Saved presentations and revisions
Each show call saves a presentation in the active project's .idea/walkthroughs/ history. Without historyId, it creates a new entry even if the description and steps match an existing presentation. Updating an entry in place requires Walkthrough Plugin 0.6.0 or later and a show tool exposing the optional historyId parameter.
Track two distinct identifiers from every successful show response:
walkthroughIdidentifies the live popup session. Each show call returns a new one; immediately use that new value forawait_walkthrough_questionand subsequent tangents.historyIdis the saved record ID followingsaved to history asin the response. Retain it with the presentation's project, description, and file/diff kind for later revisions. It stays the same when the presentation is replaced. A response sayinghistory was not savedprovides no reusable history ID.
When the user asks to revise, shorten, reorder, or preview changes to the same presentation, reuse its historyId by default. Send the complete revised description and step list with the matching show tool; for diff mode, also send the complete revised diff descriptors. This replaces the saved content, preserving its history ID and original creation time. Include any content the user wants retained: the call replaces the presentation rather than applying a partial edit. It is a saved revision, not an unsaved preview.
For a new walkthrough, or when the user asks to keep the original and make a separate copy, omit historyId. Retain the new history ID for subsequent revisions to that copy. Answer ordinary popup questions through insert_walkthrough_tangents; showing a replacement presentation is for revisions to the presentation itself.
If the history ID is missing from conversation context, recover it from prior tool responses or identify the corresponding record in the active project's .idea/walkthroughs/ by its description and contents. If the presentation cannot be identified unambiguously, ask which saved presentation the user means. Manage IDs yourself; do not ask the user to supply an API parameter or choose a record merely because it has the newest timestamp.
If an update reports an unknown history ID, a file/diff kind mismatch, or a save failure, explain the failure and stop that update. Do not retry without historyId, which would create the unwanted extra entry. If the installed tool lacks this parameter, explain that updating in place requires upgrading the IDE plugin and refreshing the client's MCP tools.
Revision example
User: "Shorten the introduction and show me the updated walkthrough without adding another history entry."
Suppose the first draft returned walkthroughId=session-a and saved to history as 20260907-120000-000-api-tour-1234abcd. After revising the full presentation, call show_walkthrough_items with:
{
"description": "API tour",
"historyId": "20260907-120000-000-api-tour-1234abcd",
"items": "[{\"text\":\"A brief overview of the API.\"},{\"text\":\"How requests move through the service.\"}]"
}
The replacement returns a new walkthroughId, such as session-b, and the same saved history ID. Immediately await questions using session-b. Further revisions reuse that history ID, leaving one saved presentation for the user to replay.
File item format
show_walkthrough_items parses items with Gson as a JSON array string. Each object:
{ "text": "...markdown...", "file": "src/Foo.kt", "line": 42 }
text(required) — GitHub-flavored markdown. Supported: headings, fenced code with syntax highlighting, lists, tables, strikethrough, GitHub alerts (> [!NOTE]), autolinks, inline HTML.file(optional) — project-relative path, forward slashes.line(optional) — 1-based line number in the current full file; navigates the editor and anchors the connector to that line.
Items without file/line render the popup without navigating.
The items parameter is a JSON string containing an array. Build a JSON array, then stringify it exactly once for the tool argument:
{
"description": "Auth middleware request flow",
"items": "[{\"text\":\"This middleware validates the bearer token before routing continues.\",\"file\":\"src/AuthMiddleware.kt\",\"line\":42},{\"text\":\"This step is conceptual and does not navigate.\"}]"
}
Do not include label or parentLabel in item objects. The plugin ignores input labels for top-level items and assigns child labels from the parentLabel tool argument.
Diff item format
show_diff_walkthrough_items parses payload with Gson as a JSON object string containing diffs and items.
{
"diffs": [
{
"id": "foo-main-to-pr",
"file": "src/Foo.kt",
"leftCommit": "1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b",
"rightCommit": "9f8e7d6c5b4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c"
}
],
"items": [
{
"text": "This new branch handles the null result.",
"diffId": "foo-main-to-pr",
"diffFile": "src/Foo.kt",
"diffSide": "right",
"line": 42
}
]
}
diffs[] (descriptors) fields:
id(required) — unique within the payload; items reference it viadiffId.file— project-root-relative path shared by both sides (non-rename case).leftFile/rightFile— use these instead offilefor renames; they may differ.leftCommit/rightCommit(required) — Git commit hashes (prefer full hashes) that Git can resolve locally. Refs/short hashes may be accepted but full hashes are stable for history replay.
items[] fields:
text(required) — same markdown rules as file items.diffId(required) — must match a descriptor'sid.diffFile(optional but recommended) — item path. If omitted, the parser falls back to the descriptor'sfile, thenrightFile, thenleftFile. For renames, include the side-specific path matchingdiffSide.diffSide(required) —"left"or"right".line(required) — 1-based line in the chosen side's full file text at that commit, not a patch hunk line.
Side rules:
- Use
rightfor added or modified new code. - Use
leftfor removed/old code. - Use
rightfor unchanged context lines, unless the step specifically discusses the old version.
Pass payload as a JSON string (stringify once):
{
"description": "Review PR #123: null-handling in Foo",
"payload": "{\"diffs\":[{\"id\":\"foo-main-to-pr\",\"file\":\"src/Foo.kt\",\"leftCommit\":\"1a2b...\",\"rightCommit\":\"9f8e...\"}],\"items\":[{\"text\":\"This new branch handles the null result.\",\"diffId\":\"foo-main-to-pr\",\"diffFile\":\"src/Foo.kt\",\"diffSide\":\"right\",\"line\":42}]}"
}
Do not submit file contents in any field. Submit commit hashes; the plugin renders a native IDEA diff from the Git revisions.
Diff commits
For diff walkthroughs, submit commit hashes, not file text. The plugin requires the Git4Idea bundled plugin and resolves revisions through the platform's Git cache.
For PR walkthroughs:
- Fetch the PR branch and the target branch if needed.
- Resolve the merge base between the target branch and the PR head.
- Use the merge-base commit as
leftCommit. - Use the PR head commit as
rightCommit. - For every walkthrough item, verify the anchor line in the selected side at that commit.
For single-commit walkthroughs, use the first parent as leftCommit and the commit being explained as rightCommit.
For branch walkthroughs, use the merge base of the target branch and topic branch as leftCommit and the topic branch head as rightCommit.
Prefer full 40-character SHAs over refs/short hashes — they are stable for history replay even after branches move.
Line Numbers (mandatory verification)
Line numbers must be correct — the connector visibly points at that line. Never estimate from a diff, commit message, memory, or LSP output.
File walkthrough lines are 1-based lines in the current full file in the working tree.
For each file item with a line:
- Read the file.
- Confirm the line number matches the symbol/expression the step describes.
- Use
rg -nto find candidate anchors when available; ifrgis unavailable, usegrep -n. - Treat search output as a candidate only; re-read the current file before using the line number.
Diff walkthrough lines are 1-based lines in the selected side's full file text at that commit, not patch hunk line numbers. For each diff item:
- Inspect that exact file at that exact commit on the requested side, e.g.
git show <rightCommit>:src/Foo.kt | nl -ba(use<leftCommit>andleftFile/filefordiffSide: "left"). - Confirm the line number matches the symbol/expression the step describes in that revision.
- Re-verify after any rebase or force-push — line numbers move when commits change.
Labels
Top-level items are auto-labeled 1, 2, 3, … in order. Do not set labels yourself.
Child steps inserted via insert_walkthrough_tangents are auto-labeled by appending .N to the parent: tangents under 3 become 3.1, 3.2. Nested tangents under 3.1 become 3.1.1, etc. Pass the exact parentLabel returned by await_walkthrough_question.
Description field
A short human-readable phrase, shown in the project's walkthrough history (.idea/walkthroughs/). Treat it as the title a user will scan a week later. Examples:
"Auth middleware request flow""How WalkthroughPopupSurface paints the connector""Review PR #123: null-handling in Foo""Commit abc1234: extract DiffWalkthroughSession"
Avoid generic phrases like "Code walkthrough", "Tour", or "Diff".
Writing step content
Each step is one focused idea anchored to one location. The popup is ~560×300px — readable, but not a doc page.
Good step text:
- Opens with the what in one sentence, then the why if non-obvious.
- Uses inline code for identifiers:
`WalkthroughSession`. - Uses fenced code blocks for snippets longer than a line or two.
- Quotes the actual surrounding code only when the reader needs more than what's visible at the anchored line.
A walkthrough is a narrative, not a file dump. If two consecutive steps differ only in line number, merge them or rewrite to show progression.
Suggested step count for a single walkthrough: 3–8 top-level items. Longer than that, the user loses the thread; shorter than that, a comment would have done the job.
Q&A loop: answering tangents
await_walkthrough_question returns either:
dismissed(literal string) — stop the loop.- a body like
parentLabel=3\nquestion=Why is this dispatched on the EDT?
For a request to revise the presentation itself, follow Saved presentations and revisions, then restart the question loop with the new walkthroughId. For an ordinary follow-up question:
- Investigate as you normally would (read files, search, inspect commits, run commands).
- Build a small
itemsarray — usually 1–3 child steps — addressing the question. - Call
insert_walkthrough_tangentswith the exactparentLabelfrom the question. - Loop back to
await_walkthrough_questionwith the samewalkthroughId.
Tangent items must use the same kind as the parent walkthrough: file items for a file walkthrough, diff items (with diffId, diffFile, diffSide, line referring to descriptors from the original show_diff_walkthrough_items call) for a diff walkthrough. The plugin parses the items array according to the stored session kind.
Tangent items use the same field shape as top-level items of that kind. Give them anchor fields when the answer is anchored somewhere specific; for file walkthroughs you may omit file/line when the answer is purely conceptual. For diff walkthroughs, tangent items should normally still anchor to one of the original diff descriptors.
If the question is unanswerable (out of scope, hallucinated premise), still respond with at least one tangent item that says so plainly — silence leaves the user staring at a spinner.
Authoring workflow
[ ] 1. Clarify the goal — what should the user understand after stepping through this?
[ ] 2. Choose file vs diff mode based on the user's request
[ ] 3. Pick anchor points — files/lines (file mode) or descriptor+side+line (diff mode)
[ ] 4. For diff mode: resolve commit hashes (merge base + head, parent + commit, etc.)
[ ] 5. Verify each anchor line by reading the file (file mode) or `git show <sha>:<path>` (diff mode)
[ ] 6. Draft the items array, one focused step per anchor
[ ] 7. Reuse historyId for a revision; omit it for a new presentation or separate copy
[ ] 8. Call the matching show tool; retain its new walkthroughId and saved history ID
[ ] 9. Enter the await_walkthrough_question loop; respond to each question; stop on dismissed
Common mistakes
- Skipping the await loop. The user can't ask questions if you don't wait. Don't call a show tool and then end your turn.
- Creating a new history entry for each revision. Reuse the saved presentation's
historyId; the livewalkthroughIdis a separate value and changes on every show call. - Mixing file and diff items in one session. Use the matching show tool; tangents must match the parent session kind.
- Using a file walkthrough for "what changed". PR / commit / branch / patch review must use
show_diff_walkthrough_items. - Submitting file contents in the diff payload. Pass commit hashes only.
- Using patch hunk line numbers for diff items.
lineis into the side's full file text at the commit, not the diff hunk. - Stale or remembered line numbers. Copying line numbers from a previous session, a diff, or a tool output that wasn't a real file/commit read. Always re-verify.
- Passing items or payload as a list/object instead of a string. Both
itemsandpayloadare JSON strings. Stringify. - Manually setting
labelorparentLabelon items. The plugin assigns labels. Authors only passparentLabeltoinsert_walkthrough_tangents, never inside an item. - Generic descriptions.
"Walkthrough"or"Diff"is useless in history; the description is searchable metadata. - Walls of text per step. The popup is small; break content across steps anchored to the relevant lines instead.
- One step per line of a function. Group related lines under one anchor; the connector only points at one line per popup.
File walkthrough example
User: "Walk me through how the MCP toolset shows a walkthrough."
After reading the current files and verifying the line numbers:
{
"description": "How show_walkthrough_items wires an MCP call to the editor popup",
"items": "[{\"text\":\"The MCP framework invokes `showWalkthroughItems` by reflection from an `@McpTool` method.\",\"file\":\"src/main/kotlin/com/forketyfork/walkthrough/ShowWalkthroughItemsToolset.kt\",\"line\":43},{\"text\":\"The active project comes from the coroutine context, not from a tool parameter.\",\"file\":\"src/main/kotlin/com/forketyfork/walkthrough/ShowWalkthroughItemsToolset.kt\",\"line\":226},{\"text\":\"UI work runs on the EDT before the tool touches the editor and popup state.\",\"file\":\"src/main/kotlin/com/forketyfork/walkthrough/ShowWalkthroughItemsToolset.kt\",\"line\":80},{\"text\":\"`showWalkthroughSession` resolves the first anchor before creating the popup session and handing the items to the UI.\",\"file\":\"src/main/kotlin/com/forketyfork/walkthrough/WalkthroughOrchestrator.kt\",\"line\":31}]"
}
Then immediately call await_walkthrough_question with the returned walkthroughId.
If it returns:
parentLabel=3
question=What happens if there's no active editor?
Build the tangent answer after re-reading the relevant code, then call insert_walkthrough_tangents:
{
"walkthroughId": "abc123",
"parentLabel": "3",
"items": "[{\"text\":\"If no active editor is available, `showWalkthroughSession` returns null and the tool reports `No active editor` to the MCP client.\",\"file\":\"src/main/kotlin/com/forketyfork/walkthrough/ShowWalkthroughItemsToolset.kt\",\"line\":82}]"
}
Loop back to await_walkthrough_question. Continue until it returns dismissed.
Diff walkthrough example
User: "Review my PR #123 that adds null-handling to Foo."
After resolving the merge base (1a2b...) and PR head (9f8e...), fetching both commits locally, and verifying each anchor line via git show <sha>:<path> | nl -ba:
{
"description": "Review PR #123: null-handling in Foo",
"payload": "{\"diffs\":[{\"id\":\"foo-pr\",\"file\":\"src/Foo.kt\",\"leftCommit\":\"1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b\",\"rightCommit\":\"9f8e7d6c5b4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c\"}],\"items\":[{\"text\":\"The new guard returns early when the lookup misses, replacing the previous NPE path.\",\"diffId\":\"foo-pr\",\"diffFile\":\"src/Foo.kt\",\"diffSide\":\"right\",\"line\":42},{\"text\":\"The old code dereferenced the result unconditionally on this line.\",\"diffId\":\"foo-pr\",\"diffFile\":\"src/Foo.kt\",\"diffSide\":\"left\",\"line\":38}]}"
}
Then immediately call await_walkthrough_question with the returned walkthroughId and stay in the loop. Any tangents must also be diff items referencing diffId: "foo-pr" and the appropriate side and line at the same commits.