Привет, Хабр! Меня зовут Андрей Слесаренко — frontend‑разработчик с опытом работы более 8 лет. Прошёл путь от джуна до тимлида, работал над разными высоко‑нагруженными проектами. В начале этого года начал активно использовать LLM‑агентов в повседневной работе — и за это время набил немало шишек.
В этой статье хочу поделиться своим опытом, где мои ожидания разошлись с результатом, а также рассказать об основных «шишках», которые я набил при работе с агентами. Поскольку я frontend разработчик, в конце приведены примеры MCP реализаций, которые стоит взять на вооружение.
Для кого эта статья: для разработчиков, которые только начинают использовать AI-ассистентов или хотят разобраться, что такое MCP. Людям, которые их активно используют, - это вряд ли будет интересно, я вас предупредил).
После прочтения этой статьи вы сможете улучшить свой опыт работы с LLM, и получать более точные результаты ответа. Отчасти мое рвение поделиться опытом, связано с тем что многие из бывших или текущих коллег не используют MCP в повседневной работе с LLM. Поэтому статья в первую очередь ориентировано на них
В статье приведены примеры кода на Javascript и VueJS 3 (где-то намеренно упрощены)
1. Введение
Я думаю, все, кто только начинал пользоваться LLM, сталкивались с историей, когда агент вроде решает задачу, которую вы ему дали, но идет не по тому пути, по которому вы бы хотели. Думаю, всем знакомая история, когда разрабатываете фичу в проекте, где есть специально созданные компоненты для кнопок/инпутов (например, AppButton.vue, AppInput.vue), и просите агента сделать следующее:
На странице /contacts нужно создать форму с двумя полями ввода, и кнопкой "Отправить данные".
После чего, он создает следующий компонент, игнорируя уже созданные в проекте компоненты из ui-kit.
<template>
<form @submit.prevent="onHandleSubmit">
<input name="login" type="text" v-model="loginModelRef" placeholder="Введите ваш email"/>
<input name="password" type="password" v-model="passwordModelRef"/>
<button type="submit">Отправить данные</button>
</form>
</template>
<script setup lang="ts">
const loginModelRef = shallowRef('');
const passwordModelRef = shallowRef('');
const onHandleSubmit = () => {
// Тут какая-то ваша логика для отправки данных на сервер
};
</script>┌─────────────────────────────────────────────────────────────────────────────┐
│ ❌ БЕЗ КОНТЕКСТА │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Промпт: Результат: │
│ ┌─────────────────────────┐ ┌─────────────────────────────────────┐ │
│ │ "Создай форму с двумя │ │ <input type="text" /> │ │
│ │ полями и кнопкой" │ ───► │ <input type="password" /> │ │
│ └─────────────────────────┘ │ <button>Отправить</button> │ │
│ └─────────────────────────────────────┘ │
│ ⚠️ Нативные элементы, не ваш UI-kit │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ ✅ С КОНТЕКСТОМ │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Промпт + контекст: Результат: │
│ ┌─────────────────────────┐ ┌─────────────────────────────────────┐ │
│ │ "Создай форму..." │ │ <AppInput v-model="login" /> │ │
│ │ + UI-kit: AppInput, │ ───► │ <AppInput type="password" /> │ │
│ │ AppButton │ │ <AppButton>Отправить</AppButton> │ │
│ └─────────────────────────┘ └─────────────────────────────────────┘ │
│ ✅ Используются компоненты проекта │
└─────────────────────────────────────────────────────────────────────────────┘
Проблема следующая: LLM ничего не знает о проекте. Не хватает контекста. Обычно это проблему решают следующим образом:
Добавляют в корень проекта файл
AGENTS.md- подавляющее число агентов перед обработкой вашего промпта, считывают этот файл и добавляют его содержимое в свой контекст.Если вы используете IDE Cursor, то дополнительно добавляют в
.cursor/rules/ui_kit.mdcтекст, где описано где и какие компоненты находятся в вашем проектеВручную указывают в промпте, как именно задачу нужно решить. Промпт выше начинает выглядеть как
На странице /contacts нужно создать форму с двумя полями ввода, и кнопкой "Отправить данные". Для компонента кнопки нужно использовать
@package/ui-kit/AppButton.vue, для инпутов@package/ui-kit/AppInput.vue
┌─────────────────────────────────────────────────────────────────┐
│ Способы дать контекст LLM │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1️⃣ AGENTS.md 📄 Файл в корне проекта │
│ └── Агент читает при старте │
│ │
│ 2️⃣ .cursor/rules/*.mdc 📁 Правила для Cursor │
│ └── Описание UI-kit, архитектуры │
│ │
│ 3️⃣ Руками в промпте ✍️ "Используй AppButton..." │
│ └── Работает, но утомительно │
│ │
│ 4️⃣ MCP серверы 🚀 Автоматический контекст │
│ └── AI сам запрашивает нужное │
│ │
├─────────────────────────────────────────────────────────────────┤
│ 1-3: ✋ ручное обновление │ 4: 🤖 автоматически актуально │
└─────────────────────────────────────────────────────────────────┘Примеры выше хорошо работают на небольших проектах, где не так много кода в целом: компонентов, классов, страниц, сторов и так далее. Но что делать, если у вас огромное количество кода, и вам не хочется каждый раз указывать контекст руками в промпте, или вручную поддерживать постоянно обновляющийся контекст приложения. (Который обновляется с каждым влитым PR).
Теперь расскажу вкратце о том, как работает общение с LLM, и объясню почему все решает контекст.
2. Как на самом деле работает общение с LLM
Прежде чем разбира��ься с MCP, давайте поймём, как вообще работают языковые модели - это важно для понимания, почему они иногда "галлюцинируют" и дают неработающий код.
LLM - это машина предсказаний
Большая языковая модель (LLM) — это, по сути, очень сложный предсказатель. Когда вы пишете промпт, модель не "думает" в привычном смысле. Она вычисляет: какой токен (слово, часть слова, символ) наиболее вероятно должен идти следующим?
Представьте, что вы начали фразу: "Привет, как твои...". Скорее всего, следующим словом будет "дела", верно? LLM делает то же самое — только учитывает миллиарды параметров и обучена на терабайтах текста.
Модель знает только то, на чём обучалась
Вот ключевой момент: LLM ничего не знает о вашем проекте.
Она обучалась на публичных данных — документации, Stack Overflow, GitHub-репозиториях, статьях. Если вы спросите про React или Vue - получите отличный ответ, потому что об этом написаны тысячи статей. Но если спросите: "Как у нас в проекте работает авторизация?" - модель начнёт... выдумывать.
Это не баг, а особенность. Модель не может ответить "не знаю" — она обучена генерировать правдоподобный текст. Отсюда и "галлюцинации": уверенные ответы, которые выглядят правильно, но не имеют отношения к реальности.
Формула хорошего ответа
Качество ответа LLM можно выразить простой формулой:
Качество ответа = Качество контекста + Качество промпта
Контекст - это информация, которую вы даёте модели вместе с вопросом. Чем точнее и релевантнее контекст, тем лучше ответ.
Промпт - это сам вопрос и инструкции. Чем конкретнее вы формулируете задачу, тем меньше пространства для "творчества" модели.
Пример плохого промпта:
«Сделай мне функцию авторизации»
Модель не знает: какой у вас фреймворк? Какой бэкенд? Какие типы данных? Она придумает что-то усреднённое, что скорее всего не подойдёт.
Пример хорошего промпта:
"Напиши функцию авторизации для Vue 3. Бэкенд возвращает JWT токен по endpoint POST /api/auth/login. Тело запроса: { email: string, password: string }. Ответ: { access_token: string, refresh_token: string }. Используй axios для запросов."
Здесь модель получила контекст и выдаст рабочий код.
Аналогия: LLM как гениальный стажёр
Мне нравится думать об LLM как об очень умном стажёре. Он:
Прочитал всю документацию в интернете
Знает синтаксис всех языков программирования
Видел тысячи примеров кода
Готов работать 24/7 без перерывов
Но при этом:
Никогда не видел ваш проект
Не знает вашу архитектуру и договорённости команды
Не понимает контекст бизнес-задачи
Будет уверенно нести чушь, если не дать ему нужную информацию
Что вы делаете, будучи лидом проекта, когда даёте задачу джуну? Даёте контекст, даете ссылку на документацию, скидываете ссылку на канал в рабочем мессенджере, где проходили обсуждения.
Проще говоря - даете контекст. MCP - это как раз способ автоматически выдавать контекст вашему ассистенту, в упорядоченном вами формате, отдавая только то, что нужно. Кстати, вы ещё и экономите токены в контекстном окне, и тратите меньше денег.
❌ Плохой промпт (без контекста)
┌─────────────────┐ ┌─────────────┐ ┌─────────────────┐
│ Промпт │ │ │ │ Ответ │
│ │ │ LLM │ │ │
│ "Сделай функцию │ ────► │ 🤖 │ ────► │ function auth() │
│ авторизации" │ │ ??? │ │ { ??? } │
│ │ │ │ │ │
└─────────────────┘ └─────────────┘ └─────────────────┘
│
Какой фреймворк?
Какой endpoint?
Какие типы?
│
▼
😵 Угадывает
✅ Хороший промпт (с контекстом)
┌─────────────────┐ ┌─────────────┐ ┌─────────────────┐
│ Промпт │ │ │ │ Ответ │
│ │ │ LLM │ │ │
│ "Vue 3, axios │ ────► │ 🤖 │ ────► │ Рабочий код с │
│ POST /auth │ │ 💡 │ │ правильными │
│ JWT токены..." │ │ │ │ типами и API │
│ │ │ │ │ │
└─────────────────┘ └─────────────┘ └─────────────────┘
│ ▲
│ │
└────────────────────────┘
Контекст = понимание3. Контекст — ключ ко всему
Мы подошли к контексту. Что такое контекст? Контекст в нашем случае - это текст, который понятным для языковой модели языком описывает проект и его составляющие. Это может быть как фраза: "Для запросов используй только window.fetch, и НИКОГДА не используй axios. Или указание, как в примере выше - использовать для кнопок в проекте только компонент AppButton.vue.
Тут важно помнить: LLM-модели ограничены в размере контекстного окна (обычно это 128к/200к символов). Это значит, что если мы будем писать следующий промпт:
Прежде чем решить задачу по созданию формы, изучи весь код в папке
apps/ensecai и пакете с ui-kit в папкеpackages/ui-kit
то мы выйдем за пределы контекстного окна прежде, чем получим ответ. Ведь в этих папках может быть много кода, ОЧЕНЬ много кода. Нужно найти способ, отдавать релеватную информацию в нужный момент по запросу от LLM. Ведь если другая ваша задача касается, например создания текстовых элементов, зачем модели что-то знать про компоненты кнопок и инпуты?
Пример плохого промпта:
"Сделай мне функцию авторизации" // нет контекста, LLM решит задачу по данным, которые были заложены в неё в процессе обучения (просто возьмет код из интернета)
Пример хорошего промпта:
"Мы используем JWT токены, бэкенд на FastAPI. Вот endpoint
/auth/login,вот типы User и TokenResponse. Создай функцию для авторизации пользователя"
Теперь мы подошли к задаче, как же всё-таки закидывать в контекст только то, что нужно?
4. Проблема: как автоматически давать правильный контекст?
Мы уже разобрались, что каждый раз вручную указывать контекст просто утомительно. Современные IDE имеют плагины (например copilot), которые могут анализировать ваши файлы в проекте, и учитывать их особенности (props, стили) в контексте, и верно их интерпретировать для решения задачи. Но эти плагины подходят только для файлов, которые находятся в вашем проекте.
Что делать, если контекст лежит где-то в другом месте? Например, у вас есть задача, добавить объект запроса для авторизации в системе. Обычно вы пишите такой промпт:
Добавь класс для запроса на авторизацию через url
api/v4/auth/login
Но тут встает другая проблема: как llm понять, какие параметры для запроса она принимает, и какое тело ответа будет в случае успешного / неуспешного ответа?
Решение: Model Context Protocol (MCP)
5. Что такое MCP простыми словами
Model Context Protocol - это протокол общения между AI-ассистентом и внешними источниками данных, разработанный компанией Anthropic. Этот протокол как раз призван решить проблему "жирных" промптов и огромного количества текстовых инструкций для LLM. (Например те же файлы AGENTS.md или .cursor/rules/**)
Мне очень нравится аналогия с USB разъемом. USB - это универсальный разьем для разного типа устройств. MCP - это универсальный разъем для источников контекста LLM. Данный протокол поддерживается всеми известными редакторами / IDE.
Также сторонние сервисы уже реализовали свои MCP серверы для быстрого доступа к контексту в ваших приложениях. Например, существуют такие MCP реализации как:
sentry-mcp- источник данных, который позволит llm агенту получать данные об ошибках пользователя. Пример: вы нашли в Sentry issue с багом от пользователя. Вы можете написать следующим промпт:В Sentryhttp://sentry.io/issues/12345789случилась ошибка при заходе на главную страницу. Изучи метаданные браузера и пользователя, составь отчет и предложи решение проблемы.В этом случае LLM запросит с помощью указанной в сервере tool данные по ошибке (о tools будет ниже), и сможет получить все необходимы данные. Следовательно, вам не нужно руками копировать контекст об ошибке в проект
figma-mcp- позволит вам просто скинуть ссылку на макет в Figma, и llm сможет с помощью нужной tool получить сверстанный html, который потом превратит в подходящий вашему проекту компонент(ы).Больше mcp-реализаций вы можете найти в интернете, в том числе для git, github, gitlab, jira, confluence и прочее, тысячи их. Больши список готовых реализаций лежит здесь - https://github.com/modelcontextprotocol/servers?tab=readme-ov-file
┌─────────────────────────────────────────────────────────────────────────────────┐
│ │
│ MCP Protocol │
│ (единый стандарт) │
│ │
│ ┌─────────────┐ ┌───────┐ ┌───────────────────────────┐ │
│ │ │ │ │ │ │ │
│ │ IDE │ │ │ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │
│ │ Client │◄─────────►│ MCP │◄─────────►│ │ Git │ │ API │ │ DB │ │ │
│ │ 🤖 │ │ 🔌 │ │ └─────┘ └─────┘ └─────┘ │ │
│ │ │ │ │ │ │ │
│ └─────────────┘ └───────┘ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │
│ │ │UI- │ │Know-│ │Your │ │ │
│ Cursor │ │kit │ │ledge│ │Tool │ │ │
│ Claude Desktop │ └─────┘ └─────┘ └─────┘ │ │
│ Zed │ │ │
│ ... │ MCP Servers │ │
│ │ │ │
│ └───────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘Архитектура MCP
Теперь давайте разберёмся, как MCP устроен под капотом. Не переживайте - никакой rocket science, архитектура максимально простая.
Клиент и сервер
MCP работает по классической клиент-серверной модели:
┌─────────────────┐ MCP Protocol ┌─────────────────┐
│ CLIENT │ ◄──────────────────────────► │ SERVER │
│ (IDE/ChatBot) │ │ (ваш код) │
└─────────────────┘ └─────────────────┘
Cursor, Claude, База знаний,
Zed, VS Code... Swagger, Git...Client- это ваша IDE или AI-приложение (Cursor, Claude Desktop, Zed). Клиент знает, как общаться по протоколу MCP и запрашивать данные.Server- это программа, которую пишете вы (или используете готовую). Сервер предоставляет данные: документацию, информацию о компонентах, структуру API - что угодно.
Когда AI-ассистенту нужна информация, он обращается к серверу через протокол MCP, получает ответ и использует его для генерации кода.
Как они общаются: транспорты
MCP поддерживает два способа связи между клиентом и сервером:
stdio (standard input/output) — для локальных серверов. Клиент запускает сервер как обычный процесс и общается с ним через stdin/stdout. Это самый простой вариант - именно его мы будем использовать в примерах.
SSE (Server-Sent Events) — для удалённых серверов. Работает через HTTP, позволяет запустить сервер где угодно и подключаться к нему по сети.
Для начала вам хватит stdio — это буквально "запустил скрипт, и он работает".
Три кита MCP: Tools, Resources, Prompts
MCP предоставляет три типа возможностей:
🔧 Tools - функции для вызова
Tools - это функции, которые AI может вызывать для получения или изменения данных. Самая используемая концепция.
Примеры tools:
kb_search("авторизация")- поиск по базе знаний
swagger_get_endpoint("/api/users", "POST")- получить информацию об API
git_log(limit=5)- посмотреть последние коммиты
Когда вы создаёте MCP-сервер, вы описываете: какие tools доступны, какие у них параметры, что они возвращают. AI сам решает, когда и какой tool вызвать.
📄 Resources — данные для чтения
Resources — это статические данные, которые AI может запросить: файлы, документы, конфигурации. В отличие от tools, resources просто отдают данные без какой-либо логики.
Примеры resources:
Содержимое README.md
Список файлов в директории
Конфигурация проекта
💬 Prompts — шаблоны промптов
Prompts - это готовые шаблоны запросов с параметрами. Полезно, когда у вас есть типовые сценарии использования.
Пример: шаблон "code_review" с параметром file_path, который подставляет нужный контекст для ревью кода.
На практике: в большинстве случаев вам хватит только Tools. Resources и Prompts — это дополнительные возможности для более сложных сценариев.
Как это работает: пример
Давайте посмотрим на реальный сценарий. Допустим, вы просите AI:
"Напиши функцию для получения списка пользователей из API"
Без MCP:
AI придумает какой-то endpoint /api/users, угадает структуру ответа, напишет код. С вероятностью 80% это не будет работать.
С MCP (Swagger-сервер):
1. Вы: "Напиши функцию для получения списка пользователей из API"
2. AI думает: "Мне нужно узнать структуру API"
→ Вызывает tool: swagger_search("users")
3. MCP-сервер отвечает:
"Найдено: GET /api/v2/users
Параметры: page (int), limit (int)
Ответ: { users: User[], total: int }
User: { id: string, email: string, name: string }"
4. AI генерирует код с правильным endpoint, параметрами и типамиЧто важно понять
AI сам решает, когда вызвать tool. Вы не п��шете "сначала вызови swagger_search". AI анализирует задачу и понимает, что ему нужна информация.
Сервер - это просто программа. Никакой магии. Python-скрипт на 100-200 строк, который отвечает на запросы.
Протокол стандартный. Написали сервер один раз - он работает в Cursor, Claude Desktop, Zed и любом другом MCP-совместимом клиенте.
Можно комбинировать несколько серверов. База знаний + Swagger + Git - каждый отвечает за свою область.
9. Пример реализации: MCP сервер для UI-kit
Ссылка на Github: https://github.com/TeodorDre/mcp-ui-kit-server-example
Тезисы:
Проблема: AI не знает про ваши компоненты и предлагает писать с нуля
Решение: MCP сервер, который "рассказывает" AI про доступные компоненты
Возможные tools:
ui_list_components— список компонентов
ui_get_component— детали компонента (props, slots, примеры)
ui_search— поиск по имени/описанию
ui_get_usage_examples— примеры использования
Схема данных:
Парсинг Vue SFC → извлечение props/emits/slots
Или ручная документация в JSON/YAML
Сценарий использования:
Разработчик: "Нужна кнопка с иконкой и loading состоянием"
AI: вызывает ui_search("button icon loading") → находит AppButton → предлагает готовый код с правильными props
10. Какие MCP серверы можно завести для вашего frontend проекта?
Теперь самое интересное - какие серверы реально полезны для frontend-разработки? Вот четыре идеи, которые дадут максимальный эффект.
🎨 UI-kit сервер (мой пример реализации на Node.js на Github выше)
Проблема: AI не знает про ваши компоненты и пишет <button> вместо <AppButton>.
Что делает: Даёт AI информацию о доступных компонентах, их props, slots и примерах использования.
Tools:
ui_list_components- список всех компонентов с кратким описанием
ui_get_component(name)- детальная информация: props, emits, slots, примеры
ui_search(query)- поиск по имени или описанию
Результат:
Вы: "Нужна форма с email и паролем"
AI вызывает: ui_search("input form")
AI получает: AppInput, AppPasswordInput, AppForm, AppButton
AI генерирует код с вашими компонентами ✅📋 Swagger / OpenAPI сервер (есть open-source решения, например на Github). Но вы можете написать своё.
Проблема: AI придумывает endpoints и структуры ответов.
Что делает: Парсит вашу OpenAPI спецификацию и даёт точную информацию об API.
Tools:
swagger_list_endpoints(tag?, method?)- список endpoints с фильтрацией
swagger_get_endpoint(path, method)- детали: параметры, body, response
swagger_get_schema(name)- структура модели данных
swagger_search(query)- поиск по API
swagger_generate_request(path, method, format)- генерация примера запроса
Результат:
Вы: "Напиши функцию для получения профиля пользователя"
AI вызывает: swagger_search("user profile")
AI получает: GET /api/v2/users/me → { id, email, name, avatar }
AI генерирует код с правильным endpoint и типами ✅Sentry MCP сервер (есть официальная реализация от Sentry)
Проблема: При отладке багов приходится вручную копировать stack trace и контекст из Sentry.
Что делает: Подключается к вашему Sentry-проекту и даёт AI доступ к информации об ошибках: stack trace, breadcrumbs, контекст пользователя, частота.
Tools:
sentry_list_issues(status?, level?)- список активных ошибок с фильтрацией
sentry_get_issue(id)- детали: stack trace, affected users, частота
sentry_get_events(issue_id, limit?)- последние события по ошибке
sentry_search(query)- поиск по тексту ошибки
Результат:
Вы: "Почему падает страница профиля?"
AI вызывает: sentry_search("profile")
AI получает:
TypeError: Cannot read 'avatar' of undefined
Stack: ProfilePage.vue:42 → useUserStore.ts:15
Affected: 12% пользователей
Browser: Safari 17
AI анализирует и предлагает фикс с учётом реального контекста ✅🎨 Figma MCP сервер (есть уже официальная реализация от Figma)
Проблема: Дизайнер обновил макет, а вы вручную сверяете цвета, отступы и шрифты.
Что делает: Подключается к Figma API и даёт AI доступ к дизайн-токенам, компонентам и стилям из ваших макетов.
Tools:
figma_get_styles(file_id)- цвета, типографика, эффекты из файла
figma_get_component(file_id, node_id)- свойства конкретного компонента
figma_get_tokens- дизайн-токены (если используете Variables)
figma_compare(node_id, css)- сравнить реализацию с макетом
Результат:
Вы: "Сверстай карточку пользователя как в макете"
AI вызывает: figma_get_component(file_id, "UserCard")
AI получает:
padding: 16px 24px
border-radius: 12px
background: var(--surface-secondary)
shadow: 0 2px 8px rgba(0,0,0,0.08)
AI генерирует CSS, точно соответствующий дизайну ✅11. Рекомендации
Начинайте работу постепенно: не нужно пытаться сразу на все создать mcp серверы. Обязательно идите итеративно. Подключили интеграцию для Figma - потестируйте ее неделю, поймите что нужно доработать в ваших компонентах - и правьте. И так шаг за шагом.
Теперь кажется действительно важным писать документацию по проекту: например описывать подробно пропсы/эмиты/слоты, что они делают, за что отвечают (можно комментариями в JSDoc или прямо в HTML). В примере выше, как раз описан способ как можно автоматически генерировать документацию из сурсов в вашем проекте. Можно признать, что не все люди любят читать документацию, но агенты - обязательно с ней ознакомятся, и все прочитают.
Кстати, хорошая идея даже организовать локальную базу знаний по истории задач/багов в вашем проекте. Можно описывать все технические решения в markdown файлов, и хранить их в проекте. Ну и как вы уже поняли - написать mcp сервер который уже будет изучать историю вашего проекта. И LLM например при попытке решить какую-то багу, будет искать похожие случаи в этой базе знаний.
Следите за актуальностью базы знаний вашего проекта / документации. Если где-то что-то не описано, то LLM постарается самостоятельно разобраться в исходном коде и решить, как с ним работать. Но будет хуже, если документация будет неактуальна - тогда результат будет некорректен.
Спасибо за ваше время!
