I copied a Manus

Replicate the Manus agent and explore the cutting-edge technologies of front-end and back-end interaction and LLM tool calls.
Core content:
1. The detailed process of Manus agent executing tasks: from querying the weather to drawing line charts
2. Front-end design: double-column layout, real-time communication and user interaction interface
3. Back-end architecture: FastAPI processing requests, LLM decision model and application of MCP services
Let’s look at the effect first
Input requirements: Query the weather in Beijing and draw it as a line graph.
Based on the input requirements, the intelligent agent first opens the browser to access the relevant web page. When the web page is inaccessible, it will automatically switch the web page. Finally, the intelligent agent will save and organize the data collected in the browser into files, and draw a line chart through Python scripts in a programming way.
Mission Planning:
Browse the web:
Writing the script:
Manus Replica Idea
Front-end (UI - HTML/CSS/JS): The interface that the user interacts with. We designed a two-column layout:
l Left column: The core conversation area, which displays the communication history between the user and Manus, as well as the summary information of the Manus calling tool (clickable and interactive).
l Right column: The right column is a simulation of Manus' computer, which can display files generated by Manus, browse web pages, etc.
l Real-time communication: Maintain a long connection with the backend through WebSocket to achieve streaming response and status update.
Backend (API - FastAPI): Responsible for processing frontend requests, managing WebSocket connections, and acting as a bridge between the frontend and the agent core. It uses an asynchronous framework to support concurrent connections and streaming processing.
LLM: Use DeepSeekV3 as the core decision model and combine it with MCP to implement tool calls.
rear end
MCP Server
A total of two MCP services are required in the whole process:
{ "mcpServers": { "playwright": { "command": "npx", "args": ["@playwright/mcp@latest"] }, "manus_server": { "command": "python", "args": ["manus_server.py"] } }}
Playwright:
Playwright is a modern end-to-end testing framework developed by Microsoft, designed for Web application testing. It supports multiple browsers (including Chromium, Firefox, and WebKit), provides cross-browser consistency testing capabilities, and ensures that applications perform consistently in different environments. Its core advantages lie in high reliability and fast execution. By interacting directly with the browser engine, it avoids the instability of traditional testing tools.
The application of Playwright in the LLM (Large Language Model) tool call is mainly reflected in providing LLM with an interface for interacting with web pages through its powerful browser automation capabilities. Playwright MCP is a server based on the Model Context Protocol (MCP). It uses Playwright's browser automation capabilities to enable LLM to directly control the browser to complete operations such as opening web pages, clicking elements, and entering text. The core advantage of this tool is that it is fast, lightweight, and can generate structured data without relying on screenshots or visual models.
Playwright MCP supports multiple browsers, such as Chromium, Firefox, and WebKit, and is compatible with multiple programming languages, including TypeScript, JavaScript, Python, .NET, and Java. It provides two modes: the default snapshot mode (Snapshot Mode) and the visual mode (Vision Mode), of which the snapshot mode is more suitable for fast and efficient structured data interaction.
manus_server:
Since the tools provided in Playwright cannot fully meet the needs of the intelligent agent, it is necessary to build some additional MCP services to meet the functions of the intelligent agent. Therefore, I built an additional MCP service called manus_server. manus_server provides the following tools:
google_search: Search for related links
make_todo_md: Create a todo file
write_to_file: Create a file
execute_command: Execute command
MCP Client
The client implements the core processing logic through the following functions:
async def process_user_message(message: str, websocket: WebSocket): """ This is the core processing function, you need to integrate your Agent interaction logic here. Receive user messages, call Agent, and send the results back to the front end through WebSocket. """ try: manus_agent.add_message(role='user', content=message) await SendStatus("Manus is processing...", websocket).send() while True: last_message_chunk = await generate_and_send_message_chunk(websocket, manus_agent.generate) tool_executed = last_message_chunk.include_tool if not tool_executed: break send_tool = SendTool(last_message_chunk, websocket) await send_tool.send_tool() result = await manus_agent.judge_and_execute(last_message_chunk) await asyncio.sleep(0.5) await send_tool.send_result() manus_agent.add_message(role='system', content=result) # --- End --- logging.info("Finished processing user message.") except Exception as e: logging.error(f"Error in agent logic: {e}", exc_info=True) await websocket.send_text(json.dumps({"type": "error", "content": f"Agent processing error: {e}"}))
Manus computer simulation
The simulation of Manus computer is mainly realized through docker.
I used docker to build a docker container, which contains some Python running environments so that I can run the Python code written by the agent.
There needs to be a path in the docker container that is bound to a path on the local machine, and the files in the two paths are synchronized. This setting makes it easy for the backend to obtain the files generated by the agent in docker, thereby displaying the contents of the files.
def get_or_create_docker_container( container_name: str = CONTAINER_NAME, image_name: str = 'python:3.12.10', local_workspace_dir=DEFAULT_TASK_DIRECTORY, container_workspace_dir: str | None = CONTAINER_WORKSPACE_DIR, docker_client: docker.DockerClient | None = client, keep_alive_command: str = DEFAULT_KEEP_ALIVE_COMMAND, auto_remove: bool = False # Set to True to automatically remove container on exit (useful for temp tasks)): client = docker_client container = None try: # 1. Try to get the existing container print(f"Checking for existing container '{container_name}'...") container = client.containers.get(container_name) print(f"Found existing container: {container.name} (ID: {container.short_id})") # 2. Ensure the found container is running if container.status != 'running': print(f"Container '{container_name}' is not running (status: {container.status}). Starting...") try: container.start() # Wait a moment for the container to fully start time.sleep(2) container.reload() # Refresh container state if container.status != 'running': # If start failed, raise an error logs = container.logs(tail=10).decode('utf-8', errors='ignore') raise RuntimeError( f"Failed to start existing container '{container_name}'. " f"Current status: {container.status}.\nLast logs:\n{logs}" ) print(f"Container '{container_name}' started successfully.") except Exception as e: print(f"ERROR: Unexpected error while starting container '{container_name}': {e}") raise else: print(f"Container '{container_name}' is already running.") except NotFound: # 3. If container Not Found, create it print(f"Container '{container_name}' not found. Creating new container...") # Prepare volumes if specified volumes = {} if local_workspace_dir and container_workspace_dir: host_path = Path(local_workspace_dir).resolve() # Use absolute path host_path.mkdir(parents=True, exist_ok=True) # Ensure host dir exists print(f"Ensuring local directory exists: {host_path}") volumes[str(host_path)] = {'bind': container_workspace_dir, 'mode': 'rw'} print(f"Mapping local '{host_path}' to container '{container_workspace_dir}'") # Check/Pull Image print(f"Checking/pulling image: {image_name}...") client.images.get(image_name) # Create and start the container print(f"Creating and starting container '{container_name}' from image '{image_name}'...") container_config = { "image": image_name, "name": container_name, "command": keep_alive_command, "volumes": volumes if volumes else None, "working_dir": container_workspace_dir if container_workspace_dir else None, "detach": True, "tty": True, "stdin_open": True, "auto_remove": auto_remove, # Add restart policy if desired, eg, restart_policy={"Name": "unless-stopped"} } # Remove None values from config container_config = {k: v for k, v in container_config.items() if v is not None} container = client.containers.run(**container_config) # Wait a moment and verify time.sleep(2) container.reload() if container.status == 'running': print(f"Successfully created and started container: {container.name} (ID: {container.short_id})") else: logs = container.logs(tail=10).decode('utf-8', errors='ignore') raise RuntimeError( f"Created container '{container_name}' failed to start. " f"Current status: {container.status}.\nLast logs:\n{logs}" ) # Final verification (should be redundant if logic above is correct, but safe) if not container or container.status != 'running': # This path should ideally not be reached if errors were raised correctly above raise RuntimeError(f"Failed to obtain a running container instance for '{container_name}'.") print(f"Container '{container.name}' is ready and running.") print("-" * 30) return containersleep(2) container.reload() if container.status == 'running': print(f"Successfully created and started container: {container.name} (ID: {container.short_id})") else: logs = container.logs(tail=10).decode('utf-8', errors='ignore') raise RuntimeError( f"Created container '{container_name}' failed to start. " f"Current status: {container.status}.\nLast logs:\n{logs}" ) # Final verification (should be redundant if logic above is correct, but safe) if not container or container.status != 'running': # This path should ideally not be reached if errors were raised correctly above raise RuntimeError(f"Failed to obtain a running container instance for '{container_name}'.") print(f"Container '{container.name}' is ready and running.") print("-" * 30) return containersleep(2) container.reload() if container.status == 'running': print(f"Successfully created and started container: {container.name} (ID: {container.short_id})") else: logs = container.logs(tail=10).decode('utf-8', errors='ignore') raise RuntimeError( f"Created container '{container_name}' failed to start. " f"Current status: {container.status}.\nLast logs:\n{logs}" ) # Final verification (should be redundant if logic above is correct, but safe) if not container or container.status != 'running': # This path should ideally not be reached if errors were raised correctly above raise RuntimeError(f"Failed to obtain a running container instance for '{container_name}'.") print(f"Container '{container.name}' is ready and running.") print("-" * 30) return container
The agent can directly operate the docker container through the command line. This code is implemented in the call of the execute_command tool. The execute_command tool will directly operate the docker container.
@mcp.tool()def execute_command(command: str) -> str: """Execute the command and return the result""" workdir = CONTAINER_WORKSPACE_DIR exit_code, output = container.exec_run(["/bin/sh", "-c", command], workdir=workdir, stream=False, demux=False) output_str = output.decode('utf-8').strip() if output else "" return f"{output_str}"
Building an intelligent agent like Manus is no easy task. The robustness of prompts, the ability to plan complex tasks, the handling of tool execution errors, multi-tool collaboration, and security are all areas that require continuous investment.
In the future, we envision Manus being able to integrate more types of tools, have stronger long-term memory and planning capabilities, and provide users with end-to-end solutions in more complex scenarios.