Как я добавил в браузерного AI-агента поддержку MCP за вечер
- четверг, 30 апреля 2026 г. в 00:00:16

"Эта статья о том, как я n0x из просто болталки сделал агента который научился открывать браузер, делать скриншоты и выполнять команды
Вы когда-нибудь разговаривали с AI, и он в ответ на просьбу «открой Яндекс» писал вам: «Вот ссылка: https://yandex.ru»?
Я — да. И каждый раз мне хотелось сказать: «Спасибо, капитан Очевидность, я и сам это знаю».
Проблема в том, что большинство LLM-приложений — это просто болталки. Они генерируют текст, но не могут сделать что то полезное. А что, если бы AI мог управлять браузером? Открывать страницы, делать скриншоты, выполнять JavaScript?
В этой статье я расскажу, как добавил в проект n0x поддержку MCP (Model Context Protocol) — и научил AI-агента открывать сайты по команде «открой …».
n0x — это open-source AI-воркстейшн, который работает полностью в браузере. LLM, RAG, Python-песочница, генерация изображений. Всё это крутится на WebGPU и WASM. Нет сервера, нет API-ключей, ваши данные не покидают ваш компьютер.
Проект уже умеет многое, но есть нюанс: агент мог только болтать. Он вызывает свои внутренние тулы (поиск, Python, память), но не может взаимодействовать с внешним миром.
А мне хотелось, чтобы на фразу «открой Хабр» он мог реально открыть вкладку с habr.com.
Так я решил добавить MCP.
MCP — это протокол, который позволяет LLM-приложениям вызывать внешние инструменты через стандартизированный интерфейс. Придуман он был в Anthropic (создатели Claude), но стал стандартом по всей экосистеме.
Выглядит это так:
[Пользователь] → [LLM] → [MCP Client] → [MCP Server] → [Инструмент] ↓ Результат выполнения
MCP Server — это отдельный процесс, который предоставляет набор тулов. Например, chrome-devtools-mcp даёт возможность управлять браузером: открывать URL, делать скриншоты, выполнять JS.
LLM в ответе пишет что-то на подобии:
{"tool": "chrome_devtools_navigate", "args": {"url": "https://habr.com"}}
MCP клиент перехватывает объект, вызывает сервер, получает результат и отдаёт обратно LLM для генерации финального ответа.
В n0x используется React + Zustand + Web Workers. MCP-клиент работает в отдельном воркере, чтобы не блокировать UI.
Вот схема:
┌─────────────────────────────────────────────────────────────┐ │ UI (React) │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │ │ ChatInput │ │ AgentTrace │ │ McpToolDrawer │ │ │ └──────┬──────┘ └──────┬──────┘ └──────────┬──────────┘ │ └─────────┼────────────────┼────────────────────┼────────────┘ │ │ │ ▼ ▼ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Zustand Store │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ useAgent.ts │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌─────────────┐ │ │ │ │ │ parseToolCall│ │ executeTool │ │ buildPrompt │ │ │ │ │ └──────┬───────┘ └──────┬───────┘ └──────┬──────┘ │ │ │ └─────────┼──────────────────┼──────────────────┼────────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ useMCP.ts │ │ │ │ (Zustand store + Worker communication) │ │ │ └─────────────────────────┬──────────────────────────────┘ │ └────────────────────────────┼────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Web Worker │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ mcp.worker.ts │ │ │ │ • Client (StreamableHTTPClientTransport) │ │ │ │ • Tool registration & state management │ │ │ └─────────────────────────┬──────────────────────────────┘ │ └────────────────────────────┼────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ MCP Server │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ chrome-devtools-mcp + supergateway │ │ │ │ (отдельный процесс на localhost:8080/mcp) │ │ │ └────────────────────────────────────────────────────────┘ │
Каждый уровень отвечает за своё:
UI — отображает панель управления тулами (McpToolDrawer)
useAgent.ts — парсит ответы LLM и вызывает тулы
useMCP.ts — Zustand-хранилище и коммуникация с воркером
mcp.worker.ts — реальные HTTP-запросы к MCP-серверу
MCP Server — внешний процесс (chrome-devtools-mcp)
async function connectToServer(url: string) { const serverUrl = new URL(url); const transport = new StreamableHTTPClientTransport(serverUrl); const client = new Client({ name: "n0x-mcp-client", version: "1.0.0" }); await client.connect(transport); const { tools } = await client.listTools(); return { client, tools }; }
Ничего сложного — создаём транспорт, подключаем клиент, получаем список тулов.
Самое интересное для меня было — научить LLM понимать естественный язык:
function parseToolCall(text: string) { // Паттерны для распознавания команд навигации const navPatterns = [ /(?:открой|open|goto|перейди|загрузи)\s+(?:https?:\/\/)?([a-zA-Z0-9][a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(?::\d+)?(?:\/[\w\-./?%&=]*)?)/i, /^(?:открой|open)\s+([\w\-]+(?:\.[\w\-]+)+)/i, ]; for (const pattern of navPatterns) { const match = text.match(pattern); if (match) { let url = match[1]; if (!url.startsWith('http')) url = 'https://' + url; return { thought: "", tool: "mcp_navigate", // имя тула из MCP args: { url } }; } } // ... остальной парсинг JSON }
Благодаря этой конструкции, фраза «открой яндекс» агент возвращает структурированный вызов тула, а не просто текст.
Когда MCP-тулы загружаются, я на всякий случай добавил короткие алиасы:
for (const tool of server.tools) { if (tool.isEnabled) { toolkit[tool.name] = async (args) => { return await mcpState.callTool(server.url, tool.name, args); }; // Алиасы для удобства пользователей if (tool.name.includes('navigate')) { toolkit['open'] = toolkit[tool.name]; toolkit['goto'] = toolkit[tool.name]; } } }
После этого стало можно писать «open google.com» вместо длинного chrome_devtools_navigate.
Чтобы LLM понимала, когда надо вызывать тулы, я добавил в системный промпт секцию:
BROWSER CONTROL TOOLS: • http___localhost_8080_mcp_navigate — OPEN ANY URL IN BROWSER. CRITICAL INSTRUCTIONS: - When user asks to "open", "go to", "visit", "открой", "перейди" — use navigate tool IMMEDIATELY. - Do NOT respond with just a text link. - ALWAYS add https:// if not specified.
Пользователь пишет: «открой хабр»
Агент распознаёт команду и вызывает MCP-тул navigate
Chrome открывает новую вкладку с habr.com
Агент отвечает: «✓ Открыл https://habr.com»
Всё происходит достаточно быстро. Никаких лишних кликов.
Подключение
Есть два процесса, которые вместе создают мост между n0x и браузером Chrome:
n0x (браузер) → supergateway (прокси) → chrome-devtools-mcp → Chrome (с отладкой)
Для начала надо запустить Chrome с remote debugging
chromium --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-profile
Проверить, что Chrome запущен правильно:
# В отдельном терминале curl http://localhost:9222/json/version
Дальше надо запустить supergateway с chrome-devtools-mcp
npx supergateway --stdio "npx chrome-devtools-mcp@latest --browser-url=http://127.0.0.1:9222" \ --port 8080 --outputTransport streamableHttp
И указать путь в MCP Tools http://localhost:8080/mcp
Вот и все.
Сейчас я добавил только открытие URL. Но MCP открывает огромные возможности:
Скриншоты — «сделай скриншот текущей страницы»
Выполнение JS — «найди на странице все заголовки h1»
Взаимодействие с DOM — «кликни на кнопку “Войти”»
Заполнение форм — «заполни поле поиска словом “MCP”»
В планах сделать полноценного AI-ассистента, который реально работает в браузере, а не просто общение.
За один вечер я добавил в n0x поддержку MCP. Мне понравилось то, на сколько это просто сделать агента, который может управлять браузером. Главное что я понял: MCP — это мост между LLM и реальным миром. И он уже существует прямо в браузере. Если захотите повторить — все исходники открыты в репозитории n0x.
Полезные ссылки: