MCP server providing Claude Code with full programmatic control over Manus.im via the official Manus API v2. Implements all 30 documented endpoints, 3 composite tools for common scenarios, and a local webhook receiver with RSA-SHA256 verification.
Manus MCP
An MCP server that gives Claude Code full programmatic control over Manus.im through the official Manus API v2. Implements all 30 documented endpoints, 3 composite tools for common workflows, and a local webhook receiver with RSA-SHA256 signature verification.
- Status: production-ready (single-user) — v0.1.1
- Language: Python 3.11+
- Transport: stdio (native to Claude Code)
- Base URL:
https://api.manus.ai
Verified coverage (v0.1.1)
| Layer | Metric |
|------|---------|
| Unit tests | 60+ (server.py dispatch, webhook ASGI app, secret-leak guard, schema stability) |
| Live e2e tests | 23 (including task.update / sendMessage / confirmAction / agent.update / website.publish) |
| Composite live | 4 (including the F2 reject check) |
| Webhook live e2e | 1 (cloudflared tunnel + receiver + Manus delivery) |
| Manus API endpoints exercised live | 30/30 (graceful skip only when the account lacks the prerequisite — no agents / no website) |
| Coverage | ≥ 80% (gated in CI) |
| mypy --strict | 0 errors |
| ruff check | 0 errors |
See docs/SECURITY.md and docs/RELEASE.md for the production flow.
What's included
30 direct API wrappers
| Category | Tools |
|-----------|-------------|
| Tasks (9) | manus_task_create, manus_task_detail, manus_task_list, manus_task_update, manus_task_stop, manus_task_delete, manus_task_send_message, manus_task_list_messages, manus_task_confirm_action |
| Projects (2) | manus_project_create, manus_project_list |
| Skills (1) | manus_skill_list |
| Agents (3) | manus_agent_list, manus_agent_detail, manus_agent_update |
| Files (3) | manus_file_create, manus_file_detail, manus_file_delete |
| Webhooks (4) | manus_webhook_create, manus_webhook_list, manus_webhook_delete, manus_webhook_public_key |
| Usage (3) | manus_usage_list, manus_usage_team_statistic, manus_usage_team_log |
| Connectors (1) | manus_connector_list |
| Browser (1) | manus_browser_online_list |
| Website (3) | manus_website_status, manus_website_list_checkpoints, manus_website_publish |
3 composite tools
manus_file_upload— creates a presigned URL, uploads the bytes, and waits forstatus=uploaded. Acceptspath,base64, or a publicurl.manus_task_wait— polls a task until it reaches a terminal status (stopped/waiting/error) and returns new messages plus wait details (event_id + response schema).manus_website_publish_and_wait— publishes a site and waits until it becomespublishedorfailed.
3 webhook tools (read from the local SQLite DB)
manus_webhook_events_list— list received events with filters.manus_webhook_events_get— fetch an event byevent_id.manus_webhook_events_clear— delete received events.
Total: 36 MCP tools.
Installation
cd path/to/Manus-MCP
python -m venv .venv
# Windows:
.venv\Scripts\activate
# macOS/Linux:
# source .venv/bin/activate
pip install -e ".[dev]"
API key configuration
Place a .env file in the project root (a .env.example template is provided). Both variable names are accepted:
MANUS_API_KEY=sk-...
# or, for backwards compatibility:
# ManusAPI=sk-...
Priority: process.env > .env.
Optional settings:
MANUS_BASE_URL=https://api.manus.ai
MANUS_HTTP_TIMEOUT=60
MANUS_LOG_LEVEL=INFO
Registering with Claude Code
Add the following to .claude/settings.json (project-level) or ~/.claude/settings.json (global):
{
"mcpServers": {
"manus": {
"command": "python",
"args": ["-m", "manus_mcp"],
"cwd": "path/to/Manus-MCP"
}
}
}
If MANUS_API_KEY is not set globally, pass it explicitly:
{
"mcpServers": {
"manus": {
"command": "python",
"args": ["-m", "manus_mcp"],
"cwd": "path/to/Manus-MCP",
"env": { "MANUS_API_KEY": "sk-..." }
}
}
}
Restart Claude Code — the manus_* tools will appear in the tool list.
Verification
Without launching the server:
python scripts/list_tools.py
Against the real API:
python scripts/smoke.py
Unit tests:
pytest
Lint and types:
ruff check .
mypy manus_mcp
Usage examples from Claude Code
manus_task_create { "message": { "content": "Give me a 5-bullet summary of today's AI news" } }
The response contains a task_id. Then:
manus_task_wait { "task_id": "<id>", "timeout_sec": 600 }
When it finishes you get last_assistant_message with the final reply and new_messages with the conversation history.
Uploading a file and attaching it to a task:
manus_file_upload { "source": { "path": "C:/docs/report.pdf" } }
manus_task_create {
"message": {
"content": [
{ "type": "text", "text": "Summarize this report" },
{ "type": "file", "file_id": "<file_id>" }
]
}
}
Webhook receiver (optional)
A local receiver for task_created / task_stopped events with full RSA-SHA256 signature verification per ManusAPIDocs/webhooks/security.md.
1. Configure environment variables
MANUS_WEBHOOK_PUBLIC_URL=https://your-tunnel.example.com/manus/webhook
MANUS_WEBHOOK_HOST=127.0.0.1
MANUS_WEBHOOK_PORT=8787
# MANUS_WEBHOOK_DB_PATH=... # defaults to %LOCALAPPDATA%\manus-mcp\events.db on Windows
MANUS_WEBHOOK_PUBLIC_URL must exactly match the URL Manus calls. The Manus signing payload includes this URL, so even a typo will cause every event to be rejected.
2. Start a tunnel
For example, with Cloudflare Tunnel:
cloudflared tunnel --url http://localhost:8787
or ngrok:
ngrok http 8787
3. Run the receiver
python -m manus_mcp.webhook_receiver
# or: manus-mcp-webhook --host 127.0.0.1 --port 8787
4. Register the webhook with Manus
In Claude Code:
manus_webhook_create { "url": "https://your-tunnel.example.com/manus/webhook" }
5. Read events
manus_webhook_events_list { "event_type": "task_stopped", "limit": 20 }
manus_webhook_events_get { "event_id": "..." }
manus_webhook_events_clear { "before_received_at": 1704000000 }
Architecture
manus_mcp/
├── __main__.py # stdio entrypoint
├── server.py # MCP Server bootstrap
├── config.py # pydantic-settings
├── logger.py # stderr-only logger
├── client/
│ ├── manus_client.py # async httpx client + retry
│ ├── rate_limiter.py # per-endpoint token bucket (from rate-limits.md)
│ ├── retry.py # exponential backoff + jitter
│ └── errors.py # ManusApiError / ManusNetworkError
├── schemas/ # pydantic models for each resource (tasks, projects, ...)
├── tools/ # @manus_tool registration for all 36 tools
│ ├── tasks.py projects.py skills.py agents.py
│ ├── files.py webhooks.py usage.py connectors.py
│ ├── browser.py website.py
│ └── composite.py # task_wait / file_upload / website_publish_and_wait
└── webhook_receiver/
├── signature.py # RSA-SHA256 verification using {ts}.{url}.{sha256_hex(body)}
├── storage.py # SQLite WAL
├── server.py # Starlette + uvicorn
├── tools.py # events_list / events_get / events_clear
└── __main__.py
Rate limits
The client honours the API limits (60-second sliding window) and transparently retries 429 responses with backoff + jitter. Limits come from ManusAPIDocs/getting-started/rate-limits.md:
- 10/min:
task.create,task.sendMessage - 40/min: all mutations
- 100/min: all read-only calls
- 600/min:
usage.*
Security
- The API key is never logged.
- The webhook receiver verifies signatures and rejects timestamps older than 5 minutes.
- The public key is cached for 1 hour.
- SQLite is opened in WAL mode with
check_same_thread=Falsefor safe multi-reader access.
License
MIT.