manim-server
by sagebynature
README.md
<div align="center">
# manim-server
FastAPI + MCP server for trusted Manim script rendering. (uses [Manim Community](https://www.manim.community/))
<video src="https://github.com/user-attachments/assets/9be7e5de-c89a-4c5a-9b6e-2852ae104acf" controls="controls" muted="muted" class="d-block rounded-bottom-2 border-top width-fit" style="max-height:480px; min-height: 200px">
</video>
</div>
## Install
```bash
uv sync --all-groups
```
## Configure
Create `.env` from the example:
```bash
cp .env.example .env
```
Supported keys:
```env
HOST=127.0.0.1
PORT=8000
DATA_DIR=.manim-server-data
TEMPLATE_DIR=template
MANIM_CLI_FLAGS=-ql
MANIM_TIMEOUT_SECONDS=120
```
Notes:
- `DATA_DIR` stores session JSON logs and rendered MP4 artifacts.
- `TEMPLATE_DIR` stores named full-wrapper Manim templates.
- `MANIM_CLI_FLAGS` is split like a shell command, then passed as subprocess args. Common values: `-ql`, `-qm`, `-qh`, `-ql --fps 30`.
- `--save_sections`, `--media_dir`, cache flags, scene path, and scene class are managed by the server.
## Run
```bash
make run
```
Default server: `http://127.0.0.1:8000`.
## Docker
Build the image:
```bash
make docker-build
```
Run it on `http://127.0.0.1:8000`:
```bash
make docker-run
```
Smoke-test the image:
```bash
make docker-smoke
```
The image is based on `manimcommunity/manim:v0.20.1`, matching the locked Manim dependency. That base image provides the Manim CLI plus common native rendering dependencies such as ffmpeg, Cairo/Pango, fonts, and minimal TeX Live. If a scene needs an extra TeX package, extend the image with that specific package instead of preinstalling all of TeX Live.
## REST flow
The example builds one session from five Manim snippets, renders the final scene,
then downloads the MP4. Use `Manim-Session-ID` when you want a stable id; omit it
to let the server generate one.
```bash
# POST /sessions creates a new scene session.
# Response includes sessionId, title, sectionCount, and an empty section log.
curl -sS -X POST http://127.0.0.1:8000/sessions \
-H 'content-type: application/json' \
-H 'Manim-Session-ID: five-section-demo' \
-d '{"title":"Five animated sections curl smoke"}'
# POST /sessions/<sessionId>/section appends one Manim code section.
# render=false records code without invoking Manim; cache is ignored until rendering.
curl -sS -X POST http://127.0.0.1:8000/sessions/five-section-demo/section \
-H 'content-type: application/json' \
-d '{"title":"Create blue circle","code":"circle = Circle(color=BLUE)\nself.play(Create(circle), run_time=1.0)","render":false}'
# Another section. Sections run in append order inside the generated scene.
curl -sS -X POST http://127.0.0.1:8000/sessions/five-section-demo/section \
-H 'content-type: application/json' \
-d '{"title":"Create green square","code":"square = Square(color=GREEN).shift(RIGHT * 2)\nself.play(Create(square), run_time=1.0)","render":false}'
# Adds a third shape, still only updating the session JSON log.
curl -sS -X POST http://127.0.0.1:8000/sessions/five-section-demo/section \
-H 'content-type: application/json' \
-d '{"title":"Create yellow triangle","code":"triangle = Triangle(color=YELLOW).shift(LEFT * 2)\nself.play(Create(triangle), run_time=1.0)","render":false}'
# Adds a fourth animation section.
curl -sS -X POST http://127.0.0.1:8000/sessions/five-section-demo/section \
-H 'content-type: application/json' \
-d '{"title":"Create lower line","code":"line = Line(LEFT, RIGHT, color=RED).shift(DOWN * 1.5)\nself.play(Create(line), run_time=1.0)","render":false}'
# Final section render=true appends code and runs Manim. cache defaults to "use",
# so omitting it lets Manim reuse existing cached partial movie files.
curl -sS -X POST http://127.0.0.1:8000/sessions/five-section-demo/section \
-H 'content-type: application/json' \
-d '{"title":"Create purple ellipse","code":"ellipse = Ellipse(width=3, height=1.5, color=PURPLE).shift(UP * 1.5)\nself.play(Create(ellipse), run_time=1.0)\nself.wait(0.1)","render":true}'
# GET /sessions/<sessionId>/video downloads the full MP4 from the latest render.
curl -sS -o /tmp/five-sections-full.mp4 \
-w 'status=%{http_code} content_type=%{content_type} bytes=%{size_download}\n' \
http://127.0.0.1:8000/sessions/five-section-demo/video
```
Open returned `latestRender.fullVideoUrl`, or a section URL such as
`/sessions/five-section-demo/sections/0001/video`, in a browser or video client.
Cache modes for `append_section` with `render=true`, and for `render_scene`:
- Omit `cache`, or set `"cache":"use"`: default; let Manim reuse cache.
- `"cache":"flush"`: delete Manim partial movie cache before rendering.
- `"cache":"disable"`: pass `--disable_caching` to Manim for that render.
## Session templates
`POST /sessions` accepts optional `templateId`:
```bash
curl -sS -X POST http://127.0.0.1:8000/sessions \
-H 'content-type: application/json' \
-d '{"title":"Lecture","templateId":"lecture"}'
```
Template assets are Python files under `TEMPLATE_DIR/<templateId>.py`.
By default this is `./template/<templateId>.py`. Unknown missing templates
fall back to `default.py`; if `default.py` is absent, session creation fails
until the template directory is fixed.
Bundled templates:
- `default`: plain title header.
- `clean-title`: title header with underline.
- `dark-grid`: dark background with faint coordinate grid.
- `presentation-card`: animated title card intro, then clears before sections.
Template files are full Manim scripts, not scene-body snippets. Keep the scene
class named `GeneratedScene`; Manim renders that class, and user sections are
appended at EOF at the `construct()` body indentation. Do not put dedented code
after `construct()` that would close that append point.
The reserved string literals `"__SESSION_ID__"`, `"__SESSION_TITLE__"`, and
`"__TEMPLATE_ID__"` are replaced before render. Include them where the template
needs those values:
```python
from manim import *
from manim.opengl import *
class GeneratedScene(Scene):
def construct(self):
# DO NOT EDIT: replaced by manim-server before render.
session_id = "__SESSION_ID__"
session_title = "__SESSION_TITLE__"
template_id = "__TEMPLATE_ID__"
title = Text(session_title or "Untitled").to_edge(UP)
self.add(title)
# user sections append here
```
## MCP endpoint
`http://127.0.0.1:8000/mcp`
Tools mirror REST: `create_session`, `list_sessions`, `get_session`, `close_session`, `append_section`, `render_scene`, `reset_session`.
## Thanks
Special thanks to the [Manim Community](https://www.manim.community/) and
[3Blue1Brown](https://github.com/3b1b) for their amazing work and inspiration.
## Security
Scene code is trusted Python. Do not expose this server to untrusted users or networks.
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues