Меня зовут Андрей Слесаренко — 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 постарается самостоятельно разобраться в исходном коде и решить, как с ним работать. Но будет хуже, если документация будет неактуальна – тогда результат будет некорректен.
Спасибо за ваше время!
Автор: Teodor_Dre


