A toolkit for making visual novels — by hand, with a browser-based editor, or with help from an AI assistant.
Ren'Py is the engine that runs visual novels like Doki Doki Literature Club and Slay the Princess. It's free and open source, but its scripting language is intimidating if you've never written code. This project exists so you don't have to.
There are three ways you can use it:
- Just the editor. A browser-based GUI with a Story Map, Choice View, Scene Inspector, and visual Composers for screens, stages, and menus. Click around; edit dialogue; preview the game in Ren'Py. No AI, no command line.
- The editor + an AI helper. Run the editor in one window, run an AI assistant (Claude Code, Cursor, hermes-agent) in another, both pointed at the same project. Tell the AI what scene you want; watch the GUI update as it edits.
- Pure AI assistant. Skip the GUI; let the AI do everything in the terminal. Fastest path from "I have an idea for a visual novel" to "there's a runnable game I can play."
If options 2 or 3 sound foreign, see QUICKSTART.md — it walks through "I've never used any of this" to "I can preview my own VN" in about 15 minutes.
For the under-the-hood story (80 MCP tools, four tiers, a single guarded write pipeline that keeps every edit lint-clean), keep reading.
Story Map — every label in your project as a draggable card. Kind badges (start / scene / choice / ending), drag-to-connect ports for new edges, persisted positions. Built on native pointer events; no React-Flow.
Scene Inspector — opens when you click any node. Renders the label's ordered event stream (scene, play, show, say, pause, set, jump, …) as typed cards, each with the source line number. The forms at the top edit background / music / dialogue; the Insert event button at the bottom drops in pause / setvar / show / with / flash events.
Choice View — derived player perspective. Every top-level menu:
becomes a card listing its choices as pills with if-guard badges.
Click a target to jump back to its label in the Story Map.
Library — one tabbed surface for the project's reusable pieces: Characters (cast cards with display name + accent colour), Assets (backgrounds, sprites, audio), Variables (default values), Screens (screen blocks + imagemaps), and Mini-Games (screen scaffolds). Each tab edits via the matching Tier 3 tool; agents call the same tools directly.
Localization — per-language translation coverage from
game/tl/<lang>/. Click a row to load its stale-string list; the
"+ Scaffold" button runs renpy.sh translate to bootstrap a new
language.
Themed shell — three surface themes (light / cream / dark), six
accent presets, sidebar variants (icon / labeled / palette). Every
preference persists in localStorage.
Ambient header — the top strip surfaces project state without
demanding attention: a watcher pill (last edited file, freshness), a
Recent disclosure (the last N edits with jump-to-source), and a Lint
badge that runs renpy.sh lint in the background and pops findings
inline.
LLM agents and fresh contributors: start with llms.txt for an indexed source map, then DESIGN.md for the architecture, tier model, writer pipeline, and how to add a tool or panel safely. If you're producing or commissioning assets, MEDIA.md documents the formats, naming conventions, and VN-shaped style / tone direction that lets media drop in without rework.
MCP server only (the common case — wire it into Claude Code, hermes-agent, Cursor, or any MCP client):
pip install git+https://github.com/fracturedring/renpy-mcp
renpy-mcp --fetch-sdk # downloads Ren'Py SDK to ~/.cache/renpy-mcp/
renpy-mcp # auto-picks the cached SDK; no flags needed--fetch-sdk is optional — pass --sdk /path/to/renpy-sdk (or set
$RENPY_SDK) if you already have the SDK installed. Pin a specific
version with --fetch-sdk --sdk-version 8.6.0.
--project is also optional. When omitted, the server works against
<cwd>/games/default/ and auto-scaffolds it on first run, so a fresh
conversation drops into a runnable starting state. Agents should call
new_project at the start of a conversation to get their own named
subfolder — see AGENTS.md for the happy-path flow.
For ready-to-paste MCP-server configs that fill in your local install's absolute paths, run:
renpy-mcp --print-config claude-code # emits .mcp.json
renpy-mcp --print-config hermes # emits ~/.hermes/config.yaml blockWith the RPBuilder GUI (requires a repo checkout until the frontend build ships with the wheel):
git clone https://github.com/fracturedring/renpy-mcp && cd renpy-mcp
gui/launch.sh # creates .venv, installs deps,
# builds frontend, runs launchergui/launch.sh runs the terminal launcher that:
- scans your filesystem for a Ren'Py SDK (
~/renpy-sdk,~/Downloads/renpy-*-sdk,~/Desktop/,/opt/, etc.) and offers the discovered ones, - if none exist, offers to download the current SDK from renpy.org (with a progress bar) or to paste a path,
- maintains a recent-projects list with
<browse>and<new>options, - remembers both choices in
~/.config/renpy-mcp/launcher.json(or%APPDATA%on Windows) so subsequent launches need at most a single Enter press.
After install you can also run rpbuilder directly from the activated
venv — same launcher. For the bare server without any picker, the
older gui/run.sh /path/to/project /path/to/sdk still works.
- 80 MCP tools across 4 tiers (78 default + 2 opt-in) — reads,
introspection, in-process diagnostics, lifecycle (preview / warp /
drafting / translation scaffolding / distribute), guarded write
primitives, high-level authoring intents (
new_projectscaffolds a runnable game in one call; composers stamp out screens, stages, and imagemaps from typed JSON trees), and opt-in escape hatches. Every tool description is tuned for small-model accuracy. - One-sentence-prompt friendly —
new_project+ the Tier 3 intents are written so a low-tier model driving this server through a harness (hermes-agent, Claude Code) can turn a one-line premise into a runnable, distributable VN. See AGENTS.md for the playbook andscripts/integration_drive.py/scripts/real_vn_drive.pyfor end-to-end smoke tests. - Single guarded write pipeline — every
.rpymutation (agent- driven or GUI-driven) routes throughapply_write: path containment, cross-file label uniqueness, tab → 4-space normalization, atomic writes,.rpyccleanup, unified diff in the response. Three documented exceptions (sidecar JSON files for canvas positions and diagnostic suppression, plus Ren'Py's own SDK-driven translation file emission) — see DESIGN.md §3. - In-process diagnostics complement
lint—find_invalid_jumps,find_undefined_characters,find_unused_characters,find_missing_assets,find_undefined_screens,find_unreachable_labels. Each shares a uniform response shape with per-rule suppression via.renpy-mcp/ignored_diagnostics.json. Cheap enough to call between every write. - RPBuilder browser GUI — port-based editable Story Map with
drag-to-rearrange, drag-to-connect, kind badges, and persisted
positions; player-perspective Choice View; tree-driven Scene Inspector
with typed event cards (say, scene, show, hide, play, stop, pause,
jump, call, return, with, set, menu, if) and inline insert affordances
for the five new event tools (pause, setvar, show, with-effect, flash);
Characters; Assets; Variables; Music; Mini-Games; Languages
(per-language coverage + stale-string list, with one-click
generate_translation_scaffolding); Composers (Screen Layout, Stage, ImageMap, Menu); Build (lint + per-platform distribute). Theme tokens drive light / cream / dark surfaces × six accent presets, sidebar variants (icon / labeled / palette), preferences modal, ⌘K command palette spanning panels and labels. Watcher self-write suppression keeps the GUI from echoing the agent's own edits. - No chat panel in the GUI — agent interaction happens in your
existing LLM harness pointed at the same
renpy-mcp. The file system is the integration point; the watcher fans filesystem events out to every connected WebSocket so the GUI stays live while the LLM edits. - Opt-in tiers —
--tiers 1,2,3is the default (reads + writes + intents); add4to unlock the escape hatches when the structured tools can't express what you need.
Drop a .mcp.json into any directory you open Claude Code from. Claude
auto-loads it on session start and exposes the tools as
mcp__renpy__<tool_name> (e.g. mcp__renpy__list_labels). A
.mcp.example.json ships in this repo as a starting
point:
{
"mcpServers": {
"renpy": {
"type": "stdio",
"command": "/path/to/renpy-mcp/.venv/bin/python",
"args": [
"-m", "renpy_mcp",
"--sdk", "/path/to/renpy-sdk"
]
}
}
}Add "--project", "/path/to/specific/project" if you want to pin the
session to an existing project; otherwise the server scaffolds
<cwd>/games/default/ and the agent can call new_project to branch
into a named subfolder.
Hermes-agent (NousResearch) uses YAML at ~/.hermes/config.yaml rather than
a project-local .mcp.json. Drop the contents of
.mcp.example.hermes.yaml into the mcp_servers:
key, replacing the placeholder paths. Or have the server emit the right
snippet with your actual install paths:
renpy-mcp --print-config hermes --sdk /path/to/renpy-sdk \
>> ~/.hermes/config.yaml
hermes mcp test renpy # verify the connection--print-config claude-code is the equivalent for .mcp.json. Tools show
up in hermes as mcp_renpy_<tool_name> (e.g. mcp_renpy_new_project). If
you only want the high-level authoring surface, filter to Tier 3 via
harness-level include/exclude:
"tools": {
"include": [
"mcp_renpy_new_project",
"mcp_renpy_get_project_overview",
"mcp_renpy_get_media_invariants",
"mcp_renpy_create_scene",
"mcp_renpy_create_choice_node",
"mcp_renpy_create_route",
"mcp_renpy_add_dialogue_block",
"mcp_renpy_add_character",
"mcp_renpy_add_image_alias",
"mcp_renpy_swap_background",
"mcp_renpy_set_scene_music",
"mcp_renpy_set_start_target",
"mcp_renpy_get_lint_report",
"mcp_renpy_launch_preview"
]
}Or load only specific tiers at the server level. Three useful presets:
--tiers 1,3— recommended for small models. Reads + high-level intents only (43 tools); no Tier 2 primitives competing with the composers. Tier 3 internally uses the writer pipeline directly, so authoring still works end-to-end.--tiers 1,2,3— default. Everything except the escape hatches.--tiers 1,2,3,4— addsapply_unified_diffandexec_python_in_initfor cases the structured tools can't express.
Image generation is not part of this server — hermes ships a fal
image tool built in (image_generate, requires FAL_KEY). The flow is:
hermes generates the PNG and writes it to
<project>/game/images/<name>.png, then calls
mcp_renpy_add_image_alias to register it. add_image_alias probes the
asset against MEDIA.md's invariants (1920×1080 backgrounds, 1080-tall
sprites with alpha, etc) and surfaces deviations as media_warnings on
the response — call mcp_renpy_get_media_invariants BEFORE generating to
pin the right dimensions and avoid a regenerate loop. Same pattern for
audio (drop the file into <project>/game/audio/; music is referenced
directly by path in play music / set_scene_music).
Anything that speaks MCP over stdio works — point it at
renpy-mcp --project <p> --sdk <s> and the tools register automatically.
Use gui/run.sh <project> <sdk> for production (builds the frontend on
first run and serves the SPA + API from a single FastAPI process on port
8765) or gui/dev.sh <project> <sdk> for a hot-reload backend + Vite dev
server. Both require a repo checkout — see the Install
section above.
Solo authoring (no LLM). Open the GUI, build a scene visually in the
Story Map Inspector, hit Preview to play it. Every edit goes through
renpy-mcp so the underlying .rpy stays lint-clean and writer-guarded.
LLM-assisted. Run the GUI in one window and your LLM harness in another,
both pointed at the same project. Ask the LLM to "make Mei more sympathetic
in the cafe scene"; the file watcher pushes the change to the GUI's
WebSocket and the Story Map plus Inspector refresh live. Conversely, edits
made in the GUI become visible to the LLM on its next read_character
call. No explicit coordination — the file system is the integration point.
The left rail collapsed to four panels in Phase 2; each composes the narrower views that used to be top-level rail entries.
| Rail panel | What it does |
|---|---|
| Story | Story Map (port-based editable graph — drag-to-rearrange, drag-to-connect, kind badges, persisted positions via read_canvas_positions / set_canvas_positions; toolbar adds Scene / Choice / Ending; Backspace deletes via delete_label) and Choice View (derived player perspective — every top-level menu: rendered as choice pills + if-guard badges) toggled by a mode pill. Selecting a node opens the right-docked Scene Inspector, which renders read_label_tree as typed event cards (say · scene · show · hide · play · stop · pause · jump · call · return · with · set · menu · if) with inline insert affordances and overlay Stage / Menu composers. |
| Library | Tabbed: Characters (add_character / update_character), Assets (Backgrounds / Sprites / Music / SFX from list_images + list_audio with usage-count badges), Variables (inline edit on default rows via set_variable_default), Screens (screen blocks plus the Screen Layout and ImageMap composers), Mini-Games (scaffolded screen+label pairs via add_minigame_screen_scaffold). |
| Localization | Per-language coverage bars from get_translation_coverage; click a row for the stale-string list from find_stale_translations; "+ Scaffold" runs generate_translation_scaffolding. |
| Ship | get_lint_report runner with severity-coded findings + raw-output viewer, plus per-platform build_distribution targets. |
The header carries the Preview toggle (launch_preview / stop_preview,
polled every 2s so external state changes sync without refresh), the
watcher pill, the Recent disclosure, and the ambient lint badge.
Three surface themes (light / cream / dark) × six accent presets,
sidebar variants (icon / labeled / palette), preferences modal,
⌘K command palette. All driven by CSS variables in theme.css;
preferences persist in localStorage.
Everything below is for people extending this codebase — either the MCP server, a GUI panel, or the tests. If that's not you, stop reading here; the sections above are the complete user surface.
The shortest contributor onboarding lives in CONTRIBUTING.md: where new code goes per tier, the non-negotiable invariants, testing patterns, and how to run the end-to-end smoke probes.
Alpha. 80 MCP tools (78 default + 2 opt-in), 378 tests passing in
~10 seconds. End-to-end smoke probes:
scripts/integration_drive.py (40-step in-process drive: scaffold →
author → diagnose → warp → translate → distribute) and
scripts/real_vn_drive.py (drives a project that ships fal-generated
assets all the way to a real <name>-<version>-pc.zip). The tier model,
writer pipeline, and GUI architecture are stable; tool schemas may
still shift in minor ways.
- MCP server (
src/renpy_mcp/) — tiered tool registry + one guarded write pipeline (apply_writeinproject/writer.py). Every mutation routes through it regardless of tier. - Project index (
project/scanner.py) — pragmatic regex scanner that surfaces labels, characters, images, audio, screens, variables, transforms. Refreshed after every write. - Guardrails (
guardrails/) — indent normalization, reserved-name checks, label-uniqueness checks, dialogue escaping. Pure functions; reused across tiers. - RPBuilder GUI (
gui/) — single FastAPI process spawns its ownrenpy-mcpstdio subprocess plus a watchdog observer that fans filesystem events out to WebSocket clients. REST endpoints are thin wrappers around MCP tool calls.
Deep dive in DESIGN.md.
- Tier 1 (default on) — 29 read tools (introspection, structured
label-tree read, choice graph, translation coverage, in-process
diagnostics with sidecar suppression, plus
get_recent_editsfor agent self-query of the per-process write history) + 7 lifecycle tools (launch_preview,stop_preview,get_preview_status,warp_to,set_drafting_mode,generate_translation_scaffolding,build_distribution). Lifecycle tools spawn the Ren'Py SDK; the reads never do. - Tier 2 (default on) — 27 guarded write primitives. One Ren'Py
construct per tool. Right layer for precise diffs. Includes the
Phase 4 event tools (
add_pause,add_setvar,add_show,add_with_effect,add_flash) that the Inspector's insert-event popup wires up,add_menuandadd_condition_branch(the if/elif/else block primitive — single Ren'Py construct, singleapply_writecall), plusupdate_menu_choice(line-precise rewrite of a single choice's prompt — paired with the Choice View pill editor). - Tier 3 (default on) — 15 high-level authoring intents, including
new_project(scaffolds a runnable skeleton and rebinds the session, returns anext_stepsarray steering small models towardset_start_targetafter authoring),set_start_targetitself (rewriteslabel start:to a singlejump <target>so the player actually lands on your opening), and the three composer tools (add_screen_layout,add_stage,add_imagemap). The Menu Composer panel calls the Tier 2add_menuprimitive directly — it is a single-construct emit, not a true composition. Composes multiple Tier 2 writes when the intent calls for it; the primary surface for agents. - Tier 4 (opt-in) — 2 escape hatches:
apply_unified_diff(strict context-match diff applier; supports creation, refuses deletion) andexec_python_in_init(ast-validatedinit python:block appender). Can touch arbitrary file content — that's the point.
Configure with --tiers 1,2,3,4 (default 1,2,3). See §2 of
DESIGN.md for how to pick a tier
when adding a tool.
snake_case; action-first for writes (add_say,swap_background), noun-first for reads (list_labels,read_label).- No
renpy_prefix — the harness already namespaces tools asmcp_<server>_<tool>. Doubling up wastes characters in tool names that small models have to attend to. - Stay <=25 chars where possible.
- Descriptions are written for small-model accuracy: terse, exact, one-line where feasible, extra paragraphs only for non-obvious constraints.
git clone https://github.com/fracturedring/renpy-mcp
cd renpy-mcp
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev,gui]"
# MCP server tests
pytest -q
# Frontend production build
cd gui/frontend && npm install && npm run buildThe test suite takes ~9 seconds. Some tests (test_gui_backend.py,
anything calling get_lint_report) are module-skipped when RENPY_SDK
is not set — that's expected locally without the SDK; they run in full
when RENPY_SDK points at a Ren'Py install.
Smoke test against the fixture project:
python scripts/smoke_test.py \
--project tests/fixtures/tiny_project \
--sdk $RENPY_SDKsrc/renpy_mcp/ # the MCP server
tools/
tier1_read.py # reads + lint + diagnostics (29 tools)
lifecycle.py # preview / warp / drafting / build (7 tools)
tier2_write.py # guarded write primitives (27 tools)
tier3_intents.py # high-level intents (15 tools)
tier4_escape.py # escape hatches (2 tools, opt-in)
_shared.py # helpers reused across tiers
registry.py # single dispatch point
project/
writer.py # THE write pipeline — every mutation passes through
scanner.py # project index (labels, chars, images, …)
guardrails/ # pure defensive helpers
server.py # MCP server + tier registration
config.py # ServerConfig + DEFAULT_TIERS
gui/ # browser-based editor
backend/src/renpy_mcp_gui/
app.py # FastAPI REST + WebSocket
mcp_client.py # stdio MCP subprocess client
watcher.py # watchdog observer → WebSocket fan-out
frontend/src/
panels/ # one component per left-rail panel
api/ # fetch wrapper + shared types
layout/ # Sidebar, Header
run.sh # production entrypoint (builds on first run)
dev.sh # hot-reload: backend + Vite dev server
tests/
fixtures/tiny_project/ # canonical fixture — copied into tmp_path per test
conftest.py # FIXTURE_ROOT, SDK_ROOT, parse(), default fixtures
test_tier1.py … test_tier4.py
test_gui_backend.py # SDK-gated; FastAPI TestClient end-to-end
test_lifecycle.py # preview spawn / stop with subprocess mocked
scripts/
smoke_test.py # end-to-end sanity probe
llms.txt # indexed source map for LLM agents
DESIGN.md # architecture deep-dive
README.md # this file
Short version:
- Pick a tier (see DESIGN §2).
- Add a
_my_tool(config, index) -> ToolDeffactory totools/tierN_*.pywith a JSON-schemainput_schema(additionalProperties: False, explicitrequiredlist). - Build new content in the handler, then call
apply_writeviawrite_responsefrom_shared.py. CatchWriteRejectedand returnerr(...). - Register the tool in the tier's
register()function. - Add happy-path + rejection tests in
tests/test_tierN.py, using theworkspacefixture (copies the fixture project intotmp_path). - If the GUI should expose it, add a thin FastAPI endpoint in
gui/backend/src/renpy_mcp_gui/app.py(module-scope Pydantic body model) and wire it into a panel.
Longer version with rationale: DESIGN §6.
- Animations panel — Ren'Py's ATL doesn't fit a multi-track timeline.
- Chat panel inside the GUI — adding one breaks the single-integration- point model (the file system).
- Authoritative Ren'Py parse —
ProjectIndexis a pragmatic scanner, not a parser. For true syntactic reasoning, call Ren'Py viaget_lint_report. - File deletion via
apply_unified_diff— out of scope; will land as its own tool when needed.
Full rationale in DESIGN §10.
AGPL-3.0-or-later — see LICENSE.
The project relicensed from MIT to AGPL-3.0 in commit-after-c728830 to
enable code-level adaptation from AGPL-licensed Ren'Py IDEs (notably
bluemoonfoundry/bmf-vangard-renpy-ide).
Snapshots taken before that commit remain MIT-licensed.
What AGPL means in practice for users:
- Forks and modifications must stay AGPL-3.0-or-later.
- The §13 network clause applies to the GUI: anyone who runs the GUI as a hosted service for users beyond themselves must offer the corresponding source to those users.
- MCP harnesses (Claude Code, hermes-agent, Cursor) communicate with the server over stdio as separate processes. They are not derivative works of the server merely by calling its tools.






