Что такое n0x и зачем ему MCP
n0x — это open-source AI-воркстейшн, который работает полностью в браузере. LLM, RAG, Python-песочница, генерация изображений. Всё это крутится на WebGPU и WASM. Нет сервера, нет API-ключей, ваши данные не покидают ваш компьютер.
Проект уже умеет многое, но есть нюанс: агент мог только болтать. Он вызывает свои внутренние тулы (поиск, Python, память), но не может взаимодействовать с внешним миром.
А мне хотелось, чтобы на фразу «открой Хабр» он мог реально открыть вкладку с habr.com.
Так я решил добавить MCP.
Что такое MCP (Model Context Protocol)
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 для генерации финального ответа.
как я встроил MCP в n0x
В 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)
Ключевые компоненты кода
1. Подключение к MCP-серверу (mcp.worker.ts)
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 };}
Ничего сложного — создаём транспорт, подключаем клиент, получаем список тулов.
2. Парсинг навигационных команд (useAgent.ts)
Самое интересное для меня было — научить 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}
Благодаря этой конструкции, фраза «открой яндекс» агент возвращает структурированный вызов тула, а не просто текст.
3. Алиасы для удобства (useChat.ts)
Когда 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.
4. Системный промпт с инструкциями
Чтобы 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.
Полезные ссылки:
ссылка на оригинал статьи https://habr.com/ru/articles/1029300/