Браузерные AI-агенты — штука крутая, но до недавнего времени они были как рыба на велосипеде: вроде едет, но без контекста. Модель живёт в песочнице, не знает про твои файлы, базы данных, API. Выход есть — Model Context Protocol (MCP). И сегодня я покажу, как прикрутить MCP к браузерному агенту на примере open-source проекта n0x. Без туманных обещаний — только код, WebGPU, WASM и грязные хаки.
Если вы ещё не знаете, что такое MCP, советую сначала прочитать материал про MCP от Anthropic. Там база, без которой дальше будет тяжело.
Проблема: агент в браузере — глухой и слепой
Возьмём типичного браузерного агента: он открывает страницы, кликает, скроллит, может даже заполнять формы. Всё это работает через DOM API и иногда через WebGPU для инференса моделей. Но как только агенту нужно узнать курс доллара, посчитать что-то в Google Таблицах или отправить запрос в ваш внутренний API — начинается ад. Приходится костылить iframe, Service Workers, или вообще перетаскивать данные вручную.
Модель живёт изолированно, её контекст — это только то, что она видит на странице. MCP решает это за счёт стандартного протокола, который позволяет агенту подключать внешние инструменты, базы знаний и любые API, работающие через JSON-RPC.
Решение: n0x + MCP = агент, который умеет всё
Проект n0x (версия 2.4.0 на момент апреля 2026) — это фреймворк для построения AI-агентов, которые работают прямо в браузере. Он использует WebGPU для ускорения инференса моделей (через WASM + Vulkan/Metal) и поддерживает динамическую загрузку LLM через Hugging Face Transformers.js. Но главная фишка — плагинная система, в которую мы и воткнём MCP.
Вот что нам понадобится:
- n0x SDK — сам агент, ставится через npm или CDN.
- MCP-совместимый сервер — можно взять готовый (например, MCP-Manticore для SQL или Figma MCP-сервер для дизайна), либо написать свой на Python/Node.js.
- MCP-клиент в браузере — библиотека
@modelcontextprotocol/sdkуже умеет работать с WebSocket.
Звучит логично? Тогда поехали собирать.
Пошаговый план: от нуля до рабочего агента
1 Поднимаем MCP-сервер (можно локально)
В качестве примера возьмём сервер, который даёт агенту доступ к файловой системе и умеет выполнять Python-скрипты. Запускаем на Node.js:
mkdir mcp-server && cd mcp-server
npm init -y
npm install @modelcontextprotocol/sdk
# создаём index.js
В index.js определяем два инструмента: read_file и run_python. Код сервера (минимальный, но рабочий):
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new Server({
name: "my-browser-helper",
version: "1.0.0",
}, {
capabilities: {
tools: {}
}
});
server.setRequestHandler("tools/list", async () => ({
tools: [
{
name: "read_file",
description: "Читает содержимое файла по пути",
inputSchema: {
type: "object",
properties: {
path: { type: "string" }
},
required: ["path"]
}
},
{
name: "run_python",
description: "Выполняет Python-код и возвращает результат",
inputSchema: {
type: "object",
properties: {
code: { type: "string" }
},
required: ["code"]
}
}
]
}));
server.setRequestHandler("tools/call", async (request) => {
const { name, arguments: args } = request.params;
if (name === "read_file") {
const fs = await import("fs/promises");
const content = await fs.readFile(args.path, "utf-8");
return { content: [{ type: "text", text: content }] };
}
if (name === "run_python") {
const { execSync } = await import("child_process");
try {
const output = execSync(`python3 -c \`${args.code}\``, { encoding: "utf-8" });
return { content: [{ type: "text", text: output }] };
} catch (e) {
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
}
}
throw new Error("Tool not found");
});
const transport = new StdioServerTransport();
await server.connect(transport);
Запускаем: node index.js. Сервер готов слушать. Но для браузера нужен WebSocket. Переключаем транспорт:
import { WebSocketServerTransport } from "@modelcontextprotocol/sdk/server/websocket.js";
const transport = new WebSocketServerTransport({ port: 3001 });
await server.connect(transport);
console.log("MCP server running on ws://localhost:3001");
Важно: Если вы используете HTTPS (а в продакшне иначе нельзя), WebSocket должен быть защищённым — wss://. Для локальной разработки подойдёт ws://, но браузер может блокировать смешанный контент, если страница открыта по HTTP. Учитывайте.
2 Инициализируем n0x агента с MCP-клиентом
Ставим n0x через CDN (или npm, если используете сборщик):
<script type="module">
import { Agent } from "https://cdn.n0x.ai/v2.4/agent.js";
import { Client } from "https://cdn.jsdelivr.net/npm/@modelcontextprotocol/sdk@1.7.0/client/wasm/index.js";
// ...
</script>
Создаём агента, загружаем модель (например, Qwen2.5-7B-Instruct quantized для WebGPU) и подключаем MCP-клиент:
const agent = new Agent({
model: "nomic-ai/Qwen2.5-7B-Instruct-q4f16",
backend: "webgpu", // используем WebGPU
maxContextLength: 4096,
});
// Ждём загрузки модели
await agent.init();
// Подключаем MCP
const mcpClient = new Client({
name: "n0x-mcp-client",
version: "1.0.0",
});
await mcpClient.connect({
transport: new WebSocketClientTransport(new WebSocket("ws://localhost:3001"))
});
// Регистрируем инструменты MCP как функции агента
const tools = await mcpClient.listTools();
for (const tool of tools) {
agent.registerTool({
name: tool.name,
description: tool.description,
handler: async (args) => {
const result = await mcpClient.callTool(tool.name, args);
return result.content[0].text;
}
});
}
Теперь агент может вызывать read_file и run_python прямо из своего контекста. Например, если пользователь скажет: «Прочитай файл config.json и запусти скрипт, который выведет его размер», агент сам решит, какие инструменты и в каком порядке запустить.
3 Заставляем агента использовать MCP в диалоге
Самое смешное, что просто зарегистрировать инструменты мало. Нужно ещё научить LLM их применять. В n0x это делается через системный промпт. Добавляем описание инструментов в начало контекста:
const systemPrompt = `Ты — полезный AI-агент, работающий в браузере.
У тебя есть доступ к следующим инструментам:
- read_file (аргумент: path) — читает файл
- run_python (аргумент: code) — выполняет Python код
Чтобы использовать инструмент, напиши JSON вида:
{"tool": "название", "args": {...}}
После получения результата проанализируй его и дай ответ пользователю.`;
await agent.setSystemPrompt(systemPrompt);
Затем в цикле диалога парсим ответ агента. Если он содержит вызов инструмента — выполняем, результат подставляем обратно в контекст и просим модель продолжить. Это стандартная схема ReAct (Reasoning + Acting).
async function chat(userMessage) {
const response = await agent.generate(userMessage);
const toolCall = extractToolCall(response.text);
if (toolCall) {
const result = await agent.executeTool(toolCall.name, toolCall.args);
const finalResponse = await agent.generate(
`Результат выполнения ${toolCall.name}: ${result}\n\nДай ответ пользователю.`
);
return finalResponse.text;
}
return response.text;
}
function extractToolCall(text) {
const match = text.match(/\{"tool":\s*"(\w+)",\s*"args":\s*(\{[^}]+\})\}/);
if (match) {
return { name: match[1], args: JSON.parse(match[2]) };
}
return null;
}
Готово. Запускаем — и видим, как агент читает файлы и выполняет Python прямо из браузера. Магия.
Нюансы и грабли, на которые я наступил
В теории всё красиво, на практике — вот что может пойти не так.
| Проблема | Причина | Решение |
|---|---|---|
| WebSocket не соединяется | CORS или смешанный контент | Настройте сервер с Access-Control-Allow-Origin: * и используйте wss:// на продакшне |
| Модель не вызывает инструменты | Слабый системный промпт или маленький контекст | Увеличьте контекст до 8k токенов, добавьте примеры вызовов в промпт |
| WebGPU падает с ошибкой | Несовместимость драйвера или устаревший браузер | Обновите Chrome 125+, включите флаг WebGPU, попробуйте backend: "wasm" как fallback |
| Сервер отказывается выполнять Python | Безопасность: exec может быть запрещён | Используйте Docker для изоляции или перепишите на безопасный executor (например, Pyodide) |
Отдельно хочу предупредить про безопасность. Если ваш MCP-сервер запущен на локальной машине, и вы подключаетесь к нему из браузера — любой скрипт на странице может вызывать инструменты. Используйте ограничение по происхождению (origin) или токен аутентификации. В примере выше сервер не проверяет, кто к нему подключился. Для продакшна добавьте проверку заголовка Sec-WebSocket-Protocol или используйте JWT.
Ещё один важный момент: не пытайтесь загрузить модель через WebGPU на машинах без дискретной видеокарты. Интегрированная графика Intel или старые AMD часто не поддерживают FP16 вычисления, и инференс будет в 10 раз медленнее, чем на CPU. Для продакшна лучше запускать модель на сервере, а браузеру оставить только лёгкий MCP-клиент. Почитайте про LM Studio MCP — там показано, как запустить агента локально без GPU.
Как не надо делать: типичные ошибки
Вот кусок кода, который я написал на первой итерации. Он упал с ошибкой, но объяснит, почему нельзя тупо копировать:
// НЕПРАВИЛЬНО: асинхронный вызов без await
const tools = mcpClient.listTools(); // вернёт Promise, а не массив
agent.registerTool(tools[0]); // tools[0] undefined
Ошибка начинающих — забыть await. Но ещё хуже: регистрировать инструменты до того, как агент инициализирован. В n0x agent.init() загружает модель и выделяет память, и только после этого можно добавлять кастомные обработчики. Порядок важен.
Вторая ошибка — парсить вызов инструмента регуляркой. Если модель сгенерирует JSON с лишними пробелами или в markdown-коде — всё сломается. Лучше попросить модель выводить только JSON (без пояснений) и парсить через JSON.parse, обернув в try-catch. Или использовать structured output — у n0x есть поддержка response_format: { type: "json_object" }.
Что дальше: превращаем агента в мультиинструментальную платформу
После того как базовая связка работает, можно расширять.
- Подключить несколько MCP-серверов одновременно — агент сам выберет нужный по описанию инструмента.
- Добавить сабагентов (о чём я писал в статье про LEGO-агентов) — каждый сабагент имеет свой MCP-контекст, а главный агент координирует их.
- Использовать MCP-сервер для доступа к WordPress: почитайте как автономно писать и публиковать посты через MCP.
- Интегрировать с IntelliJ IDEA через MCP-сервер для IDE — тогда браузерный агент сможет управлять средой разработки.
Есть и более экзотические варианты: подключить финансовую онтологию через FIBO MCP-сервер или добавить голосовой ввод/вывод через Lemon Slice. Возможности ограничены только фантазией и временем.
Лично я сейчас экспериментирую с тем, чтобы агент сам мог создавать новые MCP-инструменты на лету — генерировать код, деплоить его на сервер и тут же его использовать. Это уже похоже на AGI, но пока что это просто цепочка вызовов. Если хотите посмотреть, как я это реализовал — загляните в мой пост про BlueMouse. Там есть кусок про динамическую регистрацию инструментов.
И последнее: не забывайте про лицензии. Многие MCP-серверы распространяются под MIT, но некоторые (например, для коммерческих баз данных) требуют покупки лицензии. Если строите продукт — проверьте.
Когда-нибудь все браузерные агенты будут по умолчанию уметь подключать MCP. Но уже сейчас вы можете сделать это сами. Вопрос только в том, какой следующий инструмент вы дадите своему агенту. Если придумаете что-то безумное — пишите, мне интересно.