MCP server
Retake's MCP server lets your coding agent read the notes you leave in the dock, see what happened around them, say it's on them, reply, and mark them done. Changes show on the note in the dock as they happen.
Setup
Register it once. It's a stdio server started with npx -y retake-dev mcp; every client takes that command.
With the skill
The retake skill sets this up for you: it adds the server to the project's .mcp.json (Claude Code) or .cursor/mcp.json (Cursor), next to any servers already there. Your agent asks once to turn the retake server on (in Claude Code, when the project is next opened, or run /mcp).
{
"mcpServers": {
"retake": {
"command": "npx",
"args": ["-y", "retake-dev", "mcp"]
}
}
}Claude Code
From your app's folder:
claude mcp add retake -- npx -y retake-dev mcpCursor
The same mcpServers entry in .cursor/mcp.json in the project (or ~/.cursor/mcp.json for every project).
Codex
codex mcp add retake -- npx -y retake-dev mcpOr by hand, in ~/.codex/config.toml:
[mcp_servers.retake]
command = "npx"
args = ["-y", "retake-dev", "mcp"]Any other client
Windsurf, Zed, VS Code and the rest: add a stdio server named retake with command npx and arguments -y retake-dev mcp.
The server talks to a running Retake. Start your app with Retake first (npx retake-dev ., or next dev with Retake's proxy.ts), then ask your agent to look at the notes.
How it finds your dev server
Retake writes .retake/server.json (its URL and token) when it starts, and removes it when it stops. The MCP server looks for that file in the folder it was started in, then each parent. If your client starts it somewhere else, point it at the server yourself:
npx -y retake-dev mcp --url http://localhost:3014
RETAKE_URL=http://localhost:3014 npx -y retake-dev mcp # the same, from the environment--port 4000 is short for --url http://localhost:4000.
The loop
list_notesto see what's open.get_notefor one note's details (example), andget_momentfor what happened around its moment.acknowledgebefore editing. The pin in the dock turns to acknowledged, the dock moves to the note's timeline and, with separate code on, that timeline's code goes on disk, so the edit lands there.- Change the code. Your dev server hot-reloads it as usual.
resolvewith a one or two sentence summary. It shows on the note, and says so if the edit landed on another timeline.
To keep going without being asked, an agent can call watch_notes: it waits until you add a note or reply to one, then returns what changed.
Tools
Twelve tools. Times are written as the dock shows them (00:06.38); a timeline is its name or id, and defaults to the one on show.
Notes
| Tool | Parameters | Does |
|---|---|---|
list_notes | status? open (default: pending and acknowledged), pending, acknowledged, resolved, dismissed or all | Lists notes with their id, status, text, timeline, moment or range, selector, component, source, animation and reply count |
get_note | id | Everything about one note as text: the request, the element, its animation (or a group's) with an exact edit, the moment and timeline, whose code is on disk, and the conversation |
watch_notes | timeout_seconds? (default 60, at most 600) | Waits for a new note or a reply from you; returns the notes that changed (each with new: true or false), or an empty list on timeout |
The recording
| Tool | Parameters | Does |
|---|---|---|
get_moment | id (a note), or timeline? at selector?; before_seconds? after_seconds? (default 5 each) | What happened around a moment: the user's actions, requests, routes, the animations on or near the element (start, end, values, how far along) and when the screen changed most |
get_timeline_events | timeline? from? to? selector? limit? (default 60, at most 200) | The same for a stretch of a timeline, in order |
get_animation | id (a note) or clip (with timeline?); at? or from? to? | An animation's timing and every keyframe, and any recording time mapped onto its own clock, in CSS %, Motion times and GSAP seconds. A group note gives each member's |
Answering
| Tool | Parameters | Does |
|---|---|---|
acknowledge | id, message? | Sets the note to acknowledged and moves the dock (and, with separate code, the files) to its timeline; a message is added as a reply |
reply | id, text | Adds a reply without changing the status |
resolve | id, summary | Sets the note to resolved, with the summary as a reply; warns when the edit landed on another timeline's code |
Timelines and code
| Tool | Parameters | Does |
|---|---|---|
get_active_timeline | none | The timeline on show, every timeline with its parent, branch point and code version, its open notes, and whose code is on disk |
checkout_timeline | timeline, force? | Puts a timeline's code on disk and moves the dock there; returns the files that changed. force takes the files while another note's timeline is held for an agent |
get_code_diff | timeline? (default: the one on disk), against? (default: where it started) | The files a timeline's code changed and a unified diff |
An unknown id comes back as an error that says so (“no note with id …; list_notes shows the ids”), and so does a dev server that isn't running.
What they return
get_note, get_moment, get_timeline_events, get_animation, checkout_timeline and get_code_diff return text; acknowledge, resolve and reply a short confirmation; the others JSON. Generated from Retake's MCP code for the example note, a toast that slid in after a save:
Timeline "Timeline 2" around note nmg3k2x1a9f ("Ease this out instead of linear, and start it 60ms later") on #toasts > li.toast:nth-of-type(2):
Window: 00:04.88 → 00:07.38 of a 00:07.84 recording; the moment is 00:06.38.
Animations on or near #toasts > li.toast:nth-of-type(2) [Toast]:
- (on) 00:06.24 → 00:06.59 (350ms) transform transition on #toasts > li.toast:nth-of-type(2) [Toast]; transform translateY(16px) → none; running at the moment, 40% through (clip 7)
- (on) 00:06.24 → 00:06.59 (350ms) opacity transition on #toasts > li.toast:nth-of-type(2) [Toast]; opacity 0 → 1; running at the moment, 40% through (clip 8)
At the moment it is mid-animation: transform transition, opacity transition.
What happened (user actions, routes, page loads, requests):
- 00:04.98 focus "Title" (#draft-title)
- 00:05.01–00:05.40 typed 7 chars in #draft-title (now "Q3 plan")
- 00:05.98 click "Save" (#save)
- 00:05.99 POST /api/drafts → 200 (240ms)
- 00:06.38 ◆ the moment
Last action before the moment: click "Save" (#save) at 00:05.98 (400ms earlier). Next after: none.
Other animations in this window (1):
- 00:06.00 → 00:06.60 (600ms) animation `spin` on #save > svg.spinner; running at the moment, 63% through (clip 9)Timeline "Timeline 2":
Window: 00:05.00 → 00:07.00 of a 00:07.84 recording.
What happened (user actions, routes, page loads, requests):
- 00:05.01–00:05.40 typed 7 chars in #draft-title (now "Q3 plan")
- 00:05.98 click "Save" (#save)
- 00:05.99 POST /api/drafts → 200 (240ms)
Animations in this window (3):
- 00:06.00 → 00:06.60 (600ms) animation `spin` on #save > svg.spinner (clip 9)
- 00:06.24 → 00:06.59 (350ms) transform transition on #toasts > li.toast:nth-of-type(2) [Toast]; transform translateY(16px) → none (clip 7)
- 00:06.24 → 00:06.59 (350ms) opacity transition on #toasts > li.toast:nth-of-type(2) [Toast]; opacity 0 → 1 (clip 8)Animation of note nmg3k2x1a9f on #toasts > li.toast:nth-of-type(2):
Animation: transform transition on this element (clip 7)
350ms, delay 0ms, 1 iteration, fill none, easing linear on the whole effect
runs recording 00:06.24 → 00:06.59; local 0 = recording 00:06.24
keyframes (offset · values · easing to the next), only the first and last were recorded:
0 transform translateY(16px) linear
1 transform none
Recording 00:06.38: local 140ms, progress 0.4, eased 0.4, segment 0 → 1 (linear) at 40%
as CSS: 40% of the keyframes · as Motion times: 0.4 · as GSAP: 0.14s into the tween (0.14s after its delay){
"active": {
"id": 2,
"name": "Timeline 2",
"forkAt": "00:04.21",
"parent": "Timeline 1",
"codeVersion": null
},
"timelines": [
{
"id": 1,
"name": "Timeline 1",
"parent": null,
"forkAt": "00:00.00",
"codeVersion": null
},
{
"id": 2,
"name": "Timeline 2",
"parent": 1,
"forkAt": "00:04.21",
"codeVersion": null
}
],
"openNotes": [
{
"id": "nmg3k2x1a9f",
"status": "pending",
"text": "Ease this out instead of linear, and start it 60ms later",
"timeline": "Timeline 2",
"at": "00:06.38",
"selector": "#toasts > li.toast:nth-of-type(2)",
"component": "Toast",
"source": "/src/components/Toast.tsx:42",
"animation": "transform +140ms",
"replies": 0
}
],
"codeTimelines": "on",
"checkedOut": {
"id": 2,
"name": "Timeline 2",
"version": "7f3a1c5e90"
},
"code": {
"Timeline 1": {
"fork": "7f3a1c5e90",
"head": "7f3a1c5e90",
"changed": 0
},
"Timeline 2": {
"fork": "7f3a1c5e90",
"head": "7f3a1c5e90",
"changed": 0
}
}
}Resolved note nmg3k2x1a9f.After the agent's edit landed in Timeline 2, with separate code on:
"Timeline 2"'s code (version b41c09e2d7) against where it started (version 7f3a1c5e90): 1 file
modified src/components/Toast.tsx
diff --git a/src/components/Toast.tsx b/src/components/Toast.tsx
--- a/src/components/Toast.tsx
+++ b/src/components/Toast.tsx
@@ -40,7 +40,7 @@ export function Toast({ title, body }: ToastProps) {
return (
<li
className="toast is-entering"
- style={{ transition: "transform 0.35s linear, opacity 0.35s ease" }}
+ style={{ transition: "transform 0.35s ease-out 60ms, opacity 0.35s ease" }}
>Files on disk are now Timeline 1's code (version 7f3a1c5e90).
wrote src/components/Toast.tsxA reply's from is agent. Agents' own replies don't wake watch_notes; yours do. The same calls are plain HTTP if you'd rather script them: see the session API.
Protocol
- JSON-RPC 2.0 over stdio, one message per line. Protocol versions 2025-06-18, 2025-03-26 and 2024-11-05.
- Tools only (no resources or prompts). The server's instructions tell the agent the loop (list, get, check the moment, acknowledge before editing, resolve with a summary), how to read a note on an animation, and how to edit each kind: CSS keyframes and transitions,
element.animate(), Motion, GSAP, script-driven and scroll-driven motion. - It answers what's in flight before exiting when the client closes its end.