CodeTour
# CodeTour
Animated, LLM-driven **guided tours of code**, pitched at a developer new to the codebase.
CodeTour is an [MCP](https://modelcontextprotocol.io) server paired with a local browser
viewer. Claude (or any MCP-driving LLM) calls CodeTour's tools step by step; the viewer
animates through the code — file tree, diffs or source excerpts, pulsing line highlights —
while the narration is shown as captions and spoken aloud via the browser's built-in
text-to-speech. The subject can be a pull request, an area of existing code, or local git
work; where there is a GitHub page behind it, every stop links back to it.
## How it works
```
Claude ──(MCP stdio)──▶ codetour ──(WebSocket)──▶ your browser
│
└──(REST)──▶ GitHub API
```
1. The LLM calls `get_pr_info` / `get_file_diff` to read the PR through CodeTour, so its
narration matches exactly what is displayed. (For a codebase tour it uses its own
Read/Grep tools to decide what to show, and CodeTour reads the files from disk.)
2. `start_tour` opens the viewer in your browser and waits for you to click **Start**
(the click also enables audio).
3. `show_overview` and a series of `show_step` calls animate through the changes. Each
call blocks until the narration finishes speaking, so the tour paces itself naturally.
4. `end_tour` shows a closing summary, with a link back to GitHub where the tour has one.
## Four ways to start a tour
A tour does not have to be about a change. Whatever the subject, the viewer, narration and
controls are the same.
| Subject | Tool | Example ask |
| --- | --- | --- |
| A GitHub pull request | `start_tour(pr_url)` | "tour PR 1234" |
| An area of existing code | `start_code_tour(title, root)` | "walk me through how auth works" |
| Local git work | `start_diff_tour(root, commit/base/head)` | "tour my uncommitted changes" |
| A repo you have not cloned | `start_code_tour(title, repo, ref)` | "explain owner/name's parser" |
Stops come in two flavours and can be mixed in one tour: `show_step` for a diff hunk and
`show_code` for an excerpt of existing source (real file line numbers, no diff markers).
That means a PR tour started with `root=` can also show the *unchanged* caller of new
code.
Source files are only ever read from inside the tour's root — paths that resolve outside
it are refused.
## Installation
Requires Python 3.11+ and [uv](https://docs.astral.sh/uv/).
```sh
git clone <this repo> && cd CodeTour
uv sync
```
Register with Claude Code at **user scope**, so the tools are available in every repo:
```sh
claude mcp add --scope user codetour -- uv --directory /path/to/CodeTour run codetour
```
Then just ask:
> Give me an executive tour of https://github.com/owner/repo/pull/123
## The `codetour` skill
The repo ships a Claude Code skill ([skills/codetour/SKILL.md](skills/codetour/SKILL.md))
that teaches Claude the craft of a good tour: choosing the right entry point, auto-detecting
the current branch's PR via `gh`, planning 3–7 stops before opening the viewer, junior-developer-level
narration style (`references/narration.md`), how to route a codebase tour
(`references/code-tours.md`), and how to recover when the viewer is closed. Install it by linking (or copying) it into your
personal skills directory:
```sh
ln -s /path/to/CodeTour/skills/codetour ~/.claude/skills/codetour
```
With the skill installed, a plain "tour this PR" in any repo does the right thing.
Restart your Claude Code session after installing the MCP server or the skill.
## GitHub authentication
Public PRs work unauthenticated (low rate limit). For private repos or a higher limit,
CodeTour uses the first of: `GITHUB_TOKEN`, `GH_TOKEN`, or the `gh` CLI's stored token
(`gh auth login`).
## Configuration
| Env var | Default | Meaning |
| --- | --- | --- |
| `CODETOUR_PORT` | `8765` | Viewer port (probes upward if busy) |
| `CODETOUR_NO_BROWSER` | unset | Set to `1` to never auto-open the browser |
| `CODETOUR_MAX_FILES` | `300` | Max changed files fetched per PR |
| `CODETOUR_MAX_PATCH_BYTES` | `100000` | Per-file diff cap before truncation |
| `CODETOUR_MAX_EXCERPT_LINES` | `400` | Longest source excerpt shown in one stop |
| `CODETOUR_EXCERPT_CONTEXT` | `6` | Default lines shown either side of a highlight |
| `CODETOUR_MAX_SOURCE_BYTES` | `2000000` | Largest source file CodeTour will read |
| `CODETOUR_ROOT` | unset | If set, tour roots are restricted to this directory |
## Viewer controls
The tour plays itself as the LLM narrates, but the viewer can take over at any point:
| Control | Action |
| --- | --- |
| `⏸` / `▶`, or space | Pause or resume, mid-sentence |
| `‹` / `›`, or ← / → | Previous / next stop, at your own pace |
| Progress dots | Jump straight to a stop |
| `↻` | Replay the whole tour from the beginning |
| Voice menu | Choose the narration voice (remembered next time) |
| `🔊` | Mute or unmute the narration |
Navigating is local to the browser: it re-narrates the stop you land on and never
disturbs the LLM. If you browse backwards or pause while the LLM is still adding stops,
the view stays where you are and new stops simply extend the dots — the viewer keeps
acknowledging them so the LLM is never left waiting. Step forward to the newest stop and
the viewer follows along live again.
## Development
```sh
uv run pytest # unit tests, including the frontend harness
uv run ruff check . # lint
uv run codetour --demo # web viewer only, plays a canned pull request tour once
uv run codetour --demo code # a tour of CodeTour's own source, via the real read path
```
`tests/frontend/dom_harness.mjs` runs `app.js` under Node against a stub DOM to test the
viewer's navigation state machine without a browser (needs `node`; no npm packages). Run
it directly for a readable pass/fail list:
```sh
node tests/frontend/dom_harness.mjs
```
Demo mode is the quickest way to iterate on the frontend: it needs no MCP client and no
GitHub access — open the printed URL, click Start, and watch the canned tour.
## MCP tools
| Tool | Purpose |
| --- | --- |
| `get_pr_info(pr_url)` | PR metadata + per-file hunk line ranges (call first) |
| `get_file_diff(pr_url, path)` | Full parsed diff for one file |
| `start_tour(pr_url, tour_title?, root?)` | Tour a pull request |
| `start_code_tour(title, root/repo, ref?, files?)` | Tour existing source, local or on GitHub |
| `start_diff_tour(root, commit/base/head?)` | Tour a local commit, branch range or working tree |
| `show_overview(narration, key_points?)` | Overview card, spoken |
| `show_step(file, narration, line_start?, line_end?, side?, style?)` | Animated stop on a diff hunk |
| `show_code(file, narration, line_start?, line_end?, context?, style?)` | Animated stop on a source excerpt |
| `end_tour(summary)` | Closing card, with a GitHub link where there is one |
| `tour_status()` | Diagnostics |
TDQS
Scored across 7 tools
Each tool has a distinctly different role: fetching PR metadata, fetching file diffs, starting the tour, showing overview/step/end screens, and checking diagnostics. No two tools overlap in purpose, making selection unambiguous.
Most tools follow a clear verb_noun pattern (get_pr_info, get_file_diff, start_tour, show_overview, show_step, end_tour). 'tour_status' is the one outlier, reading as a noun phrase rather than a verb-driven action.
Seven tools is well-scoped for a PR tour workflow: two for data retrieval, four for tour progression, and one for diagnostics. Each tool serves a necessary function without redundancy.
The tool surface covers the full tour lifecycle: retrieving PR context, reading diffs, starting the tour, showing overview and steps, ending cleanly, and diagnosing state. No obvious dead ends or missing operations for the stated purpose.