A complete implementation guide for integrating Manim (Mathematical Animation Engine) with MCP (Model Context Protocol) servers, enabling AI assistants to create programmatic animations and visualizations. See the template repository for a complete example.
![]() |
![]() |
![]() |
![]() |
- Containerized Execution: No local Manim installation required
- Multiple Output Formats: MP4, GIF, and PNG support
- Quality Settings: From low (360p) to 4K resolution
- Automatic File Management: Organized output with timestamps
- MCP Protocol Integration: Clean AI-assistant interface
Parameter | Type | Required | Default | Description |
---|---|---|---|---|
code |
string | Yes | - | Python code with Manim Scene class |
output_name |
string | No | "animation" | Base filename for output |
quality |
string | No | "medium" | Rendering quality: low/medium/high/fourk |
format |
string | No | "mp4" | Output format: mp4/gif/png |
Note: MCP servers communicate via stdio, not HTTP. The container runs a wrapper script (run_server.py
) to keep it alive. For actual MCP usage, you'll need to connect your MCP client to the container's stdio.
For testing the tool directly:
# Access the container
docker exec -it mcp-manim-server python3
# In Python, test the tool
from mcp_manim_tool import ManimTool
from pathlib import Path
import asyncio
tool = ManimTool(Path("/workspace/animations"))
result = asyncio.run(tool.create_animation(
code='''from manim import *
class Test(Scene):
def construct(self):
self.play(Write(Text("Hello World!")))
''',
output_name="test",
format="gif"
))
print(result)
- Docker and Docker Compose
This guide walks through setting up the MCP Manim integration step by step.
Before starting, ensure you have:
- Docker Desktop or Docker Engine installed
- Docker Compose v2.0+
- Git (for cloning the gist)
- At least 4GB of available RAM for rendering
git clone https://gist.github.com/AndrewAltimit/c437c9fbc9a72271969127fcbf935561 mcp-manim
cd mcp-manim
docker-compose build
This process will:
- Download the Python 3.13 slim base image
- Install system dependencies (Cairo, Pango, FFmpeg)
- Install Python packages (Manim, MCP, etc.)
- Configure the MCP server
Expected build time: 5-10 minutes depending on internet speed.
docker-compose up -d
Verify the container is running:
docker-compose ps
You should see:
NAME STATUS PORTS
mcp-manim-server Up 30 seconds (healthy)
Note: MCP servers use stdio (standard input/output) for communication, not HTTP. The container runs a wrapper script to stay alive. To actually use the MCP server, you need to connect an MCP client to the container's stdio.
To test the MCP server directly:
# Access the container
docker exec -it mcp-manim-server bash
# Run Python and test the tool
python3
>>> from mcp_manim_tool import ManimTool
>>> from pathlib import Path
>>> tool = ManimTool(Path("/workspace/animations"))
>>> # Create a simple test animation
>>> import asyncio
>>> result = asyncio.run(tool.create_animation(
... code='''from manim import *
... class Test(Scene):
... def construct(self):
... self.play(Write(Text("It works!")))
... ''',
... output_name="test"
... ))
>>> print(result)
To use this server with an MCP client (like Claude Desktop), you'll need to configure the client to connect to the container's stdio. The exact configuration depends on your MCP client.
Create a simple test animation:
from manim import *
class TestScene(Scene):
def construct(self):
text = Text("MCP Manim Works!", font_size=48)
self.play(Write(text))
self.wait()
You can customize behavior with these environment variables:
# .env
MCP_PORT=8000
MCP_LOG_LEVEL=INFO
MANIM_QUALITY_DEFAULT=medium
ANIMATION_OUTPUT_DIR=animations
If you encounter DNS issues during build:
- Add to
docker-compose.yml
:
build:
network: host
- Or use Google's DNS:
dns:
- 8.8.8.8
- 8.8.4.4
Run the verification script:
docker-compose exec mcp-server python3 -c "
import manim
import mcp
print('✅ Manim version:', manim.__version__)
print('✅ MCP imported successfully')
print('✅ Server ready for animations!')
"
- Review the Implementation Guide
- Try the Example Animations
- Check Troubleshooting if you encounter issues
To update the installation:
# Pull latest changes
git pull
# Rebuild the image
docker-compose build --no-cache
# Restart the server
docker-compose restart
This document provides technical details about the MCP server integration with Manim.
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ │ │ │ │ │
│ AI Assistant ├────►│ MCP Server ├────►│ Manim │
│ (Claude, etc) │ │ (Python async) │ │ (Container) │
│ │ │ │ │ │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│ │ │
│ Tool Request │ Execute Python │
└──────────────────────►│ in isolated env │
└────────────────────────►│
class MCPManimServer:
"""MCP Server with Manim integration."""
def __init__(self, port: int = 8000):
self.server = Server("mcp-manim-server")
self.port = port
self.project_root = Path(os.getenv('MCP_PROJECT_ROOT', '/workspace'))
self.animation_tool = ManimTool(
self.project_root / os.getenv('ANIMATION_OUTPUT_DIR', 'animations')
)
- Initializes MCP server instance
- Sets up project paths from environment variables
- Creates ManimTool instance for animation generation
The core animation creation logic:
async def create_animation(
self,
code: str,
output_name: str = "animation",
quality: str = "medium",
format: str = "mp4"
) -> Dict[str, Any]:
Process Flow:
- Validation: Check input code and extract Scene class name
- Temporary Environment: Create isolated temp directory
- Code Execution: Write Python code to file and execute Manim
- Output Management: Find generated file and copy to output directory
- Response Formatting: Return structured result with metadata
def _extract_scene_name(self, code: str) -> Optional[str]:
"""Extract the Scene class name from code."""
for line in code.split('\n'):
if 'class ' in line and '(Scene)' in line:
parts = line.split('class ')[1].split('(')[0]
return parts.strip()
return None
Simple but effective regex-based detection of Manim Scene classes.
def _build_manim_command(
self,
script_path: Path,
scene_name: str,
quality: str,
format: str
) -> List[str]:
Quality mappings:
low
: 480p15 (-ql)medium
: 720p30 (-qm)high
: 1080p60 (-qh)fourk
: 4K60 (-qk)
Format-specific flags:
- GIF:
--format=gif
- PNG:
-s
(save last frame) - MP4: Default, no extra flags
def _find_output_file(self, temp_path: Path, format: str) -> Optional[Path]:
"""Find the output file created by Manim."""
media_dir = temp_path / 'media'
pattern = extensions.get(format, '*.mp4')
for file_path in media_dir.rglob(pattern):
if 'partial_movie_files' not in str(file_path):
return file_path
Searches Manim's media directory structure, skipping intermediate files.
The implementation includes comprehensive error handling:
- Input Validation: Empty code, missing Scene class
- Execution Errors: Manim process failures with stderr capture
- Output Errors: Missing output files
- Async Exceptions: Proper exception logging and user-friendly messages
Uses Python's asyncio for non-blocking execution:
process = await asyncio.create_subprocess_exec(
*cmd,
cwd=temp_dir,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await process.communicate()
This allows the MCP server to handle multiple animation requests concurrently.
- Temporary Files: Uses Python's
tempfile.TemporaryDirectory()
for automatic cleanup - Output Organization: Timestamps in filenames prevent collisions
- Path Safety: All paths use
pathlib.Path
for cross-platform compatibility
- Container Caching: Manim cache volume persists between runs
- Parallel Execution: Async design allows concurrent animations
- Resource Limits: Docker container limits CPU/memory usage
- Output Size: File size tracking helps monitor disk usage
- Code Isolation: Each animation runs in isolated temp directory
- Container Sandbox: Docker provides additional security layer
- No File System Access: Animation code can't access host filesystem
- Resource Limits: Container prevents resource exhaustion
The design allows easy extension:
- New Parameters: Add to tool registration and ManimTool.create_animation
- Custom Scenes: Support additional Manim features through code parameter
- Output Processing: Post-process animations before returning
- Multiple Engines: Could support other animation libraries with similar interface
This document provides complete examples of animations you can create with the MCP Manim integration.
from manim import *
class SimpleDemo(Scene):
def construct(self):
# Create and animate basic shapes
circle = Circle(radius=1.5, color=BLUE, fill_opacity=0.5)
square = Square(side_length=3, color=GREEN, fill_opacity=0.5)
self.play(Create(circle))
self.play(Transform(circle, square))
self.wait()
from manim import *
class MathDemo(Scene):
def construct(self):
# Display text
title = Text("Euler's Identity", font_size=48)
self.play(Write(title))
self.play(title.animate.to_edge(UP))
# Show famous equation
equation = MathTex(r"e^{i\pi} + 1 = 0")
self.play(Write(equation))
self.wait(2)
from manim import *
import numpy as np
class GraphDemo(Scene):
def construct(self):
# Create axes
axes = Axes(
x_range=[-3, 3, 1],
y_range=[-2, 2, 1],
axis_config={"color": BLUE},
)
# Plot functions
sin_graph = axes.plot(lambda x: np.sin(x), color=GREEN)
cos_graph = axes.plot(lambda x: np.cos(x), color=RED)
# Animate
self.play(Create(axes))
self.play(Create(sin_graph), Create(cos_graph))
self.wait()
from manim import *
class ThreeDDemo(ThreeDScene):
def construct(self):
# Set up 3D axes
axes = ThreeDAxes()
# Create 3D objects
sphere = Sphere(radius=1, color=BLUE)
cube = Cube(side_length=2, color=RED)
# Position objects
sphere.shift(LEFT * 3)
cube.shift(RIGHT * 3)
# Animate with camera movement
self.set_camera_orientation(phi=60 * DEGREES, theta=45 * DEGREES)
self.play(Create(axes))
self.play(Create(sphere), Create(cube))
self.play(
Rotate(sphere, angle=PI, axis=UP),
Rotate(cube, angle=PI/2, axis=RIGHT)
)
self.wait()
See nanite_system.py
from manim import *
class UpdaterDemo(Scene):
def construct(self):
# Create a value tracker
tracker = ValueTracker(0)
# Create a number that updates based on tracker
number = DecimalNumber(0, num_decimal_places=2)
number.add_updater(lambda m: m.set_value(tracker.get_value()))
# Create a circle that changes size
circle = Circle(radius=1)
circle.add_updater(
lambda m: m.set_width(2 + tracker.get_value())
)
self.add(number, circle)
self.play(tracker.animate.set_value(3), run_time=3)
self.wait()
from manim import *
import random
class ParticleDemo(Scene):
def construct(self):
particles = VGroup()
# Create particles
for _ in range(50):
particle = Dot(
point=ORIGIN,
radius=0.05,
color=random.choice([BLUE, GREEN, YELLOW, RED])
)
particles.add(particle)
self.add(particles)
# Animate particles spreading out
animations = []
for particle in particles:
direction = np.array([
random.uniform(-1, 1),
random.uniform(-1, 1),
0
])
animations.append(
particle.animate.shift(direction * 3)
)
self.play(*animations, run_time=2)
self.wait()
{
"tool": "create_manim_animation",
"arguments": {
"code": "from manim import *\n\nclass HelloWorld(Scene):\n def construct(self):\n text = Text('Hello, World!')\n self.play(Write(text))\n self.wait()",
"output_name": "hello_world",
"quality": "medium",
"format": "mp4"
}
}
{
"tool": "create_manim_animation",
"arguments": {
"code": "<your_manim_code_here>",
"output_name": "my_animation",
"quality": "high",
"format": "gif"
}
}
{
"tool": "create_manim_animation",
"arguments": {
"code": "<your_manim_code_here>",
"output_name": "ultra_hd_animation",
"quality": "fourk",
"format": "mp4"
}
}
-
Scene Structure
- Always inherit from
Scene
(orThreeDScene
for 3D) - Use the
construct
method for your animation logic - Keep scenes focused on a single concept
- Always inherit from
-
Performance
- Use
VGroup
to batch similar objects - Minimize the number of objects for GIF exports
- Use lower quality for testing, high/4K for final renders
- Use
-
Visual Design
- Use consistent color schemes
- Add appropriate wait times between animations
- Label important elements with text
-
Code Organization
- Define helper methods for complex object creation
- Use descriptive variable names
- Comment complex animation sequences
The examples above can generate various outputs:
- MP4 Videos: Smooth, high-quality animations
- GIFs: Great for documentation and sharing
- PNG Frames: Single frames for static visualization
Example outputs are available in the assets/
directory of this gist.
Common issues and solutions when using the MCP Manim integration.
Problem: Docker build fails with network errors
Temporary failure in name resolution
Solution: Add DNS configuration to docker-compose.yml
build:
network: host # Use host network for DNS resolution
volumes:
- /etc/resolv.conf:/etc/resolv.conf:ro # Share host DNS
Problem: Build fails with missing system packages
error: Microsoft Visual C++ 14.0 is required (Windows)
No such file or directory: 'gcc' (Linux)
Solution: Ensure Dockerfile includes build dependencies
RUN apt-get update && apt-get install -y \
build-essential \
libcairo2-dev \
libpango1.0-dev \
ffmpeg
Problem: ModuleNotFoundError: No module named 'mcp'
Solution: Use the correct package name
# requirements.txt
mcp-python>=0.1.0 # NOT just "mcp"
Problem: "No Scene class found in code"
Solution: Ensure your code has a proper Scene class
from manim import *
class MyAnimation(Scene): # Must inherit from Scene
def construct(self): # Must have construct method
pass
Problem: Manim process returns non-zero exit code
Common Causes:
- Syntax errors in Python code
- Import errors - missing Manim imports
- Invalid Manim operations
Debugging Steps:
- Check the error details in the response
- Test the code locally first
- Ensure all imports are included
Problem: Animation completes but no output file
Possible Causes:
- Scene has no animations (empty construct method)
- Wrong output format specified
- Manim skipped rendering
Solution: Ensure your scene actually creates content
def construct(self):
text = Text("Hello")
self.play(Write(text)) # Must have at least one animation
self.wait() # Or a wait command
Problem: Animations take too long to generate
Solutions:
-
Use lower quality for testing
{"quality": "low"} # 480p15 instead of 1080p60
-
Reduce scene complexity
- Fewer objects
- Simpler animations
- Shorter duration
-
Enable Manim caching (already configured in docker-compose)
Problem: GIF files are too large
Solutions:
- Use MP4 format instead when possible
- Reduce animation duration
- Lower the quality setting
- Simplify the scene content
Problem: Container starts but MCP server doesn't respond
Debugging:
# Check container logs
docker-compose logs mcp-manim-server
# Verify server is running
docker-compose ps
# Test health check
docker-compose exec mcp-manim-server python3 -c "print('Server is responsive')"
Problem: Cannot write to output directory
Solution: Ensure proper volume permissions
# On host, check permissions
ls -la animations/
# Fix if needed
chmod 755 animations/
Problem: Animations fail with disk space errors
Solution: Clean up old files and Docker resources
# Remove old animations
rm -rf animations/*.mp4
# Clean Docker resources
docker system prune -f
docker volume prune -f
Problem: 3D scenes appear flat or don't render
Solution: Use ThreeDScene and set camera
class My3DScene(ThreeDScene): # Note: ThreeDScene, not Scene
def construct(self):
self.set_camera_orientation(phi=60*DEGREES, theta=45*DEGREES)
# Your 3D animations here
Problem: Text appears garbled or doesn't render
Solutions:
- Use basic fonts
- Avoid special characters in some cases
- For math, use MathTex instead of Text
# Good
equation = MathTex(r"\int_0^1 x^2 dx")
# Instead of
equation = Text("∫₀¹ x² dx") # May not render correctly
Problem: Colors don't appear as expected
Solution: Use Manim color constants
# Good
circle = Circle(color=BLUE)
# Avoid
circle = Circle(color="#0000FF") # May not work as expected
Set environment variable:
environment:
- LOG_LEVEL=DEBUG
Before using through MCP, test your Manim code:
# Inside container
docker-compose exec mcp-manim-server bash
cd /tmp
echo "your_code_here" > test.py
manim -ql test.py YourSceneName
Look in the media directory for clues:
ls -la media/videos/*/
ls -la media/images/*/
Ensure your MCP tool requests are properly formatted:
# Valid JSON
{
"tool": "create_manim_animation",
"arguments": {
"code": "...",
"output_name": "test"
}
}
If you encounter issues not covered here:
- Check Manim documentation: https://docs.manim.community/
- Review MCP documentation: https://modelcontextprotocol.io/
- Check container logs for detailed error messages
- Ensure you're using compatible versions of all dependencies
This is awesome. How do I set up the MCP server with Claude Code?