Leo MCP Server
Provides tools to delegate coding and research tasks to Brave Leo AI, including skills for refactoring, debugging, and planning, as well as fast web search, deep research, and conversation history.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Leo MCP ServerRefactor src/auth.py to use async/await and save the changes"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Leo MCP Server v10.3
Bridge between coding agents (Claude Code, Hermes) and Brave Leo AI via MCP (Model Context Protocol). Delegates execution to Leo to reduce the orchestrating agent's token usage: the agent plans, Leo executes.
Quick Start
Requirements
macOS with Brave Browser installed
Python 3.12+
Brave running with debug port: --remote-debugging-port=9222 (the server bootstraps this automatically if not already active)
Installation
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txtrequirements.txt is a generated lock file (single source of truth is
pyproject.toml). To regenerate it after changing dependencies:
uv pip compile pyproject.toml -o requirements.txtRun
python3 leo_mcp_server.pyOn startup, the server:
Acquires a single-instance lock (~/.leo_mcp_server.lock)
Bootstraps Brave with CDP port 9222 (restarting it if needed)
Starts the MCP transport at http://127.0.0.1:9223
Related MCP server: mcacp
Available Tools
Tool | Description | Key Args | Returns |
ask_leo_skill | Runs a skill (code_refiner, code_editor, senior_planner) with file injection; writes to disk by default |
| JSON |
ask_leo_result | Polls a background generation started by ask_leo_skill |
|
|
ask_leo_quick | Fast single-turn web search via Brave AI (headless Chromium) |
| JSON |
ask_leo_extensive | Deep multi-turn research via Brave AI (headless Chromium) |
| JSON |
get_conversation_history | Returns recent Leo conversation turns from the local SQLite DB |
| JSON array |
leo_next_instruction | Returns the single next atomic action (create / replace / run / done) for a locked target file. Turn 1 injects the file once to open the session; every later turn resumes the same conversation_uuid with only the raw result of the last action — no file re-injection. Loop until action == "done" |
|
|
leo_apply_edit | Applies a minimal str_replace edit to a file via the code_editor skill |
| JSON |
ask_leo_skill Skills
Skill | Use | Writes to disk |
code_refiner | Refactoring and optimization | Yes (feature-flagged) |
code_editor | Minimal str_replace edits | Yes (feature-flagged) |
senior_planner | Decomposition and planning | No |
Web Search Tools (ask_leo_quick / ask_leo_extensive)
ask_leo_quick (fast search) and ask_leo_extensive (deep research) launch
their own headless Chromium via Playwright — independent of the Brave CDP
connection on port 9222 used by ask_leo_skill. They therefore require the
Playwright browser binaries to be installed, in addition to Brave running:
source .venv/bin/activate
playwright install chromiumIf you hit BrowserType.launch: Executable doesn't exist at .../ms-playwright/...,
run the command above; it means the Playwright browser binary is missing.
Write-to-Disk (feature flag)
code_refiner can write the refactored file directly to disk instead of returning the full content to the agent. This minimizes the tokens the agent re-ingests: it receives only a compact summary (path, risk, one-line description).
Opt-in via a single environment variable; the write location is derived per-request (see below), not from a global path:
Variable | Values | Effect |
LEO_WRITE_MODE | enabled (default) | Writes the modified file to disk |
LEO_WRITE_MODE | dry_run | Reports what would be written without touching disk |
LEO_WRITE_MODE | disabled | Leo returns output only; nothing is written |
Disk writing activates only when ALL hold:
LEO_WRITE_MODE is not disabled
The skill opts into patch output (code_refiner or code_editor)
Exactly one file path is provided
Note that write mode is ENABLED by default, so code_refiner and code_editor are
effectively write-patch skills out of the box: they REQUIRE exactly one absolute
path in filepaths. Omitting filepaths (or passing more than one) is rejected
with status: "error" before any Leo round-trip — it is not treated as optional
context. The non-writing skill senior_planner accepts filepaths only as optional
injected context and never writes to disk.
Write root is resolved per request (not a global LEO_WRITE_ROOT):
If the target lives in a detectable project (AGENTS.md, pyproject.toml, package.json, Cargo.toml, go.mod, Makefile, ...), the project root is used.
Otherwise, if the target is under a temp directory (/tmp or the system temp dir), that file's own parent directory is used.
Anything else fails closed: the write is rejected. This lets one server serve multiple projects in a single session without a shared root.
Safety guarantees (enforced by patch_writer):
Per-request root containment: writes outside the resolved root are rejected (symlink-safe)
Denylist: writes to sensitive locations (~/.ssh, ~/.aws, ~/.config, ~/.gnupg, ~/Library, /etc, /usr, /bin, /sbin, /var, /System, or the bare home dir) are rejected even if they appear inside the root
Anti-TOCTOU: if the target changes on disk during the Leo round-trip, the stale write is aborted instead of clobbering newer content
Atomic writes via temp file + os.replace (no partial writes)
Backup: the previous version is saved to a .bak file before overwriting; repeated writes use .bak.1, .bak.2, ... so the original is never clobbered
Validation: malformed or truncated output is rejected before any write
Action checks: modify requires an existing file; create requires a new one
Example:
mkdir -p /tmp/leo_test && echo "def f(): return 1+1" > /tmp/leo_test/x.py
LEO_WRITE_MODE=dry_run python3 leo_mcp_server.pyRecommendation: run against a git-tracked repo. The .bak file is a secondary safety net; git diff / git checkout is the primary recovery path.
Structured Output
code_refiner, code_editor, and senior_planner support a structured mode that injects a strict output contract: a sentinel-delimited FILE_PATCH envelope for the refiner (the file content is written verbatim, never JSON-escaped), a FILE_EDIT envelope for the editor (a single OLD_STR/NEW_STR pair), and a STRUCTURED_PLAN JSON object for the planner.
Note: Leo treats CDP-injected content as data, not instructions (anti-injection by design). It may wrap output in markdown fences or add prose. The patch parser tolerates this: it locates the envelope by its sentinels (<<>> ... <<>>) within surrounding prose, and the terminal sentinel is the sole completeness signal (a truncated response is reported clearly and can be auto-continued).
Troubleshooting
connect_over_cdp times out — ws connected then silence
Symptom: BrowserType.connect_over_cdp: Timeout 30000ms exceeded; the call
log shows <ws connecting> → <ws connected> and then stops. The CDP endpoint
is healthy (curl http://localhost:9222/json/version responds, and a raw
WebSocket Browser.getVersion call succeeds).
Cause (not a version/dependency issue): Playwright's connect_over_cdp
sends Target.setAutoAttach(autoAttach=true, waitForDebuggerOnStart=true) to
every existing target and waits for each to resolve
Runtime.runIfWaitingForDebugger. Brave's Leo AI conversation iframes
(chrome-untrusted://leo-ai-conversation-entries/...) and extension service
workers (e.g. keepa) never complete that handshake, so Playwright stalls —
intermittently, once a Leo conversation tab and/or extension workers become
resident in a long-lived session.
Fix: fully quit Brave and relaunch it, then restart the server:
osascript -e 'quit app "Brave Browser"' && sleep 3
brave-debug # relaunch with --remote-debugging-port=9222
python3 leo_mcp_server.pyClosing the Leo chrome://leo-ai/ tabs and any page that emits a continuous
stream of Log.entryAdded events (e.g. an admin panel with many password
inputs) also helps prevent the stall.
Structure
leo_mcp/
├── leo_mcp_server.py # MCP server + tool definitions
├── leo_chat/ # CDP module
│ ├── config.py # Write-mode resolution (LEO_WRITE_MODE)
│ ├── execution.py # Unified Leo flow (structured + streaming)
│ ├── patch_writer.py # Single-file disk writes (validation, atomicity)
│ ├── pages/ # Brave Leo Page Object
│ ├── context/ # Prompt building
│ ├── db/ # SQLite + streaming
│ └── skills/ # Skill factory + skill_support
├── brave_search/ # Brave Search module
└── tests/ # Tests (separate from production)
├── brave_search/
├── leo_chat/ # includes test_patch_writer.py
└── integration/ # includes ask_leo_skill decision matrixTests
source .venv/bin/activate
pytest tests/ -vCoverage includes:
Brave Browser (headless, CDP)
leo_chat (skills, context, streaming)
patch_writer (dry-run, versioned backup, path traversal, denylist, TOCTOU, envelope validation, action checks)
integration (MCP server, ask_leo_skill decision matrix)
Fast verification without calling Leo (levels 1-5):
Import resolution and function presence
Per-skill structured contract injection
LEO_WRITE_MODE resolution and per-request write-root derivation
patch_writer safety guarantees
Decision matrix: disabled never writes; senior_planner never writes even when enabled
Architecture Notes
Agent plans, Leo executes: token savings come from file injection (Leo reads files, the agent does not) plus disk writes (the agent receives a summary, not the file content).
Conversation resume: pass conversation_uuid to send a follow-up without re-injecting history.
Single structured axis: one flag drives both the file patch and the JSON plan; disk writing is a separate, narrower condition.
Philosophy
Visible failure over invisible magic: explicit bootstrap and validation
User takes action over auto-repair: clear errors with resolution steps
Explicit destructive operations: disk writes are ON by default, per-skill gated (set LEO_WRITE_MODE=disabled to opt out)
Data over instructions: injected content is never treated as commands
License: MIT Author: Juan Carlos Duarte Version: 10.3 (2026-07)
This server cannot be deployed
Maintenance
Related MCP Connectors
Connect AI agents to Replynodes over the Model Context Protocol.
A comprehensive Model Context Protocol (MCP) server that enables AI assistants to interact with yo…
Telegram bridge for your MCP-compatible agent. Bidirectional, no LLM in our stack.
- QuallaaOAuthcom.quallaa
Talk to your public-facing AI from any MCP client — Claude, ChatGPT, Cursor, Cline, Windsurf.
Related MCP Servers
- AlicenseNot gradedqualityFmaintenanceBridges the Model Context Protocol with Language Server Protocol to provide AI agents with persistent access to code intelligence features including navigation, diagnostics, refactoring, and completion across 7+ programming languages.2,782 npmMIT
- AlicenseAqualityDmaintenanceBridges any MCP client (like Claude Code, Zed, VS Code) to any ACP coding agent, enabling multi-agent orchestration from a single chat interface.24103 npm9Apache 2.0
- AlicenseNot gradedqualityCmaintenanceMCP bridge for calling local coding-agent CLIs (Codex, Claude) from another agent, enabling bounded tasks like code review, verification, and bug hunting.MIT
- AlicenseAqualityAmaintenanceBridges multiple CLI coding agents (Codex, Cursor, OpenCode, Claude, Antigravity) into any MCP client, enabling delegation of prompts, parallel execution, and code review workflows.651 npmMozilla Public 2.0