javascript

Как я добавил в браузерного AI-агента поддержку MCP за вечер

  • четверг, 30 апреля 2026 г. в 00:00:16
https://habr.com/ru/articles/1029300/
n0x c MCP Tools
n0x c MCP Tools

"Эта статья о том, как я n0x из просто болталки сделал агента который научился открывать браузер, делать скриншоты и выполнять команды

Вы когда-нибудь разговаривали с AI, и он в ответ на просьбу «открой Яндекс» писал вам: «Вот ссылка: https://yandex.ru»?

Я — да. И каждый раз мне хотелось сказать: «Спасибо, капитан Очевидность, я и сам это знаю».

Проблема в том, что большинство LLM-приложений — это просто болталки. Они генерируют текст, но не могут сделать что то полезное. А что, если бы AI мог управлять браузером? Открывать страницы, делать скриншоты, выполнять JavaScript?

В этой статье я расскажу, как добавил в проект n0x поддержку MCP (Model Context Protocol) — и научил AI-агента открывать сайты по команде «открой …».

Что такое 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.

Как это выглядит со стороны пользователя

  1. Пользователь пишет: «открой хабр»

  2. Агент распознаёт команду и вызывает MCP-тул navigate

  3. Chrome открывает новую вкладку с habr.com

  4. Агент отвечает: «✓ Открыл 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.

Полезные ссылки: