MCP App Proxyfier
MCP server delivering interactive MCP Apps UI for Megamarket shopping, enabling search, product details, cart, and checkout through native interfaces within the chat.
README
MCP App Proxyfier
MCP-сервер, который отдаёт в чат с моделью интерактивные MCP Apps (официальное UI-расширение MCP, рендерится в песочнице-iframe хоста). Вместо текстовой обёртки над запросами зритель получает нативный UI прямо в диалоге.
Реализован один вылизанный флоу на настоящих данных: Megamarket — поиск товаров → грид → детальная страница → корзина → оформление.
Данные статические: каталог собран из сохранённых снапшотов реальных страниц
megamarket.ru (pages/) скриптом pnpm update:data. Сеть и браузер на демо не нужны —
сервер стартует мгновенно и отвечает одинаково при любом Wi-Fi в зале.
Сценарий живого демо (без MCP → MCP без UI → MCP с UI → MCP с UI и скиллом) — в
DEMO.md.
Структура
packages/
ui/ React + Vite; собирается в самодостаточные HTML (по одному на приложение)
server/ MCP-сервер, отдаёт UI как ui:// ресурсы + инструменты
pages/ HAR/HTML-снапшоты megamarket.ru — сырьё для pnpm update:data
UI собирается в два самодостаточных HTML-бандла — index (каркасный ping) и
megamarket; все JS/CSS встроены, внешних ссылок нет (требование песочницы-iframe).
Сервер на старте читает эти HTML и регистрирует как ui:// ресурсы.
Инструменты
| Инструмент | Вход | UI | Назначение |
|---|---|---|---|
ping |
echo? |
ping.html |
Каркасная проверка рендера iframe |
search_products |
query, filters? |
— | Поиск товаров, результат только текстом (список позиций) |
search_products_widget |
query, filters? |
megamarket.html |
Тот же поиск + грид карточек виджетом |
search_products_advised |
query, filters? |
megamarket.html |
Тот же поиск + виджет; описание обязывает прочитать skill://shopping-advisor |
get_product |
id |
megamarket.html |
Карточка товара: галерея, таблица «О товаре», описание |
get_delivery_calendar |
— | — | Сегодня/завтра + ближайшие 7 дней с днями недели |
add_to_cart |
id |
megamarket.html |
Добавляет товар в корзину |
view_cart |
— | megamarket.html |
Текущее состояние корзины |
checkout |
— | megamarket.html |
Оформляет заказ по корзине, возвращает подтверждение и очищает её |
filters — ценовой коридор priceMin / priceMax и срок доставки deliveryBy
(YYYY-MM-DD; оставляет только то, что приедет не позже).
get_delivery_calendar существует потому, что у модели нет часов: «до пятницы» она сама
в число не превратит — либо выдумает, либо отсчитает от даты обучения. Скилл обязывает
вызвать календарь до поиска, отсюда и порядок вызовов в демо.
Три варианта поиска — это ступени живого демо (без UI → с UI → с UI и методичкой). Они
существуют одновременно на одном подключении, переключение идёт формулировкой запроса, без
перезапуска сервера. Почему их три, а не один с параметром: привязка UI живёт в
_meta.ui.resourceUri на регистрации инструмента и уезжает клиенту в tools/list —
результат вызова её изменить не может.
Корзина — in-memory, одна на процесс сервера: перезапуск её обнуляет.
Ресурсы ui://
MCP Apps: самодостаточный HTML, который хост рендерит в песочнице-iframe и кормит
structuredContent результата инструмента через мост. MIME — text/html;profile=mcp-app.
| URI | Собирается из | Кто рендерит |
|---|---|---|
ui://mcp-app-proxyfier/ping.html |
packages/ui/index.html → dist/index.html |
ping |
ui://mcp-app-proxyfier/megamarket.html |
packages/ui/megamarket.html → dist/megamarket.html |
все инструменты Megamarket |
Приложение Megamarket — мини-SPA: выдача → деталка → корзина → подтверждение. Какой вид
показать, оно решает по форме пришедшего structuredContent: products — выдача,
product — деталка, cart — корзина.
Виджет интерактивный, а не картинка:
- чипы фильтров над гридом (бренд, шумоподавление) — фильтруют внутри iframe, без вызова инструмента и без нового пузыря в чате;
- клик по карточке открывает деталку (
get_productчерез мост), «Назад» возвращает в тот же отфильтрованный список — состояние фильтров переживает переход; - деталка открывается и голосом («покажи подробнее вот эти») — вид тот же самый;
- «В корзину» на карточке и на деталке — app-initiated
add_to_cart(id); ответ несёт актуальную корзину, поэтому бейдж обновляется без отдельногоview_cart.
Фильтры виджета сознательно не трогают доставку: срок задаёт агент через
filters.deliveryBy на сервере. Иначе виджет молча показывал бы то, что агент уже отсёк.
Ресурсы skill://
Методички для агента. В отличие от ui:// это не MCP Apps — рендерить нечего, это
обычный текст, который агент читает перед вызовом инструмента.
| URI | MIME | Назначение |
|---|---|---|
skill://index.json |
application/json |
Индекс скиллов — точка входа, по которой агент находит остальные |
skill://shopping-advisor/SKILL.md |
text/markdown |
Подбор товара: уточнить бюджет и сценарий, перевести бюджет в filters, сравнивать по цене и объёму отзывов, не вестись на витринную скидку |
Индекс и сами методички собираются из одного SkillDefinition
(packages/server/src/skills/skill-registry.ts), поэтому имя и описание в индексе не могут
разъехаться с ресурсом.
Данные
Каталог — packages/server/data/market.json (70 товаров, у всех есть детальная карточка).
Пересобирается из снапшотов:
pnpm update:data # разбирает pages/ → packages/server/data/market.json
pnpm update:data -- --dry-run # только показать, что распарсилось, ничего не писать
Скрипт идемпотентен: повторный прогон просто перезаписывает файл. Сеть не трогает.
Выдача поиска ограничена девятью позициями (SEARCH_RESULT_LIMIT) — грид 3×3 в узком
чат-iframe.
Сроки доставки — синтетические
packages/server/data/delivery.json не собирается из снапшотов: настоящий срок
(calculatedDeliveryDate в SSR-стейте страниц) есть ровно у одного товара каталога из 70, а
на выдаче Мегамаркет его не отдаёт вовсе. Формат подписей при этом взят у сайта: он пишет
только «Сегодня», «Завтра» и дату вида «15 июля» — «Послезавтра» у него нет.
В файле лежат дни, а не даты: дата считается в рантайме от сегодня, поэтому «Завтра» остаётся завтрашним и через месяц. Товары, которых в файле нет, получают детерминированный срок по хешу id — одинаковый между запусками, чтобы выдача не «дышала».
Флаг anc (активное шумоподавление) поднимается в «плоский» DTO из характеристик, чтобы
виджет фильтровал грид без запроса деталки на каждый товар. null означает «характеристики
нет в снапшоте», а не «шумоподавления нет».
Требования
- Node.js 22+ (рекомендуется 24)
- pnpm 11+
Сборка
pnpm install
pnpm build # сначала собирает HTML-бандлы UI, затем сервер
pnpm build сначала собирает UI (самодостаточные HTML со встроенными JS/CSS — без внешних
источников, как требует песочница-iframe), затем компилирует сервер, который на старте
читает эти HTML и регистрирует как ui:// ресурсы.
Превью виджета в браузере
Посмотреть виджет без Claude Desktop:
pnpm --filter @mcp-app-proxyfier/ui exec vite
# → http://localhost:5173/preview.html
Рендерит те же компоненты вью, что и боевое приложение, на настоящем ответе сервера.
Работают чипы фильтров, заход в карточку и возврат в отфильтрованный список. Кейс
открывается ссылкой: ?case=friday, ?case=detail, ?case=cart.
Фикстуры пересобираются с живого сервера, руками их править не надо:
pnpm build # превью читает ответы собранного сервера
pnpm update:fixtures # → packages/ui/src/preview/fixtures.json
Даты доставки в фикстуре заморожены на момент снятия (превью — про вёрстку, не про
календарь). Протухли подписи вроде «Завтра» — просто перезапустите update:fixtures.
Чего в превью нет: postMessage-моста и вызовов инструментов — «Подробнее» берёт деталку из
фикстуры, а не дёргает get_product; «В корзину» показывает снимок корзины, а не вызывает
add_to_cart. Мост и рендер iframe проверяются только вживую, в Claude Desktop (см.
«Ручная проверка рендера» ниже). В продакшен-сборку превью не попадает: pnpm build:ui
собирает только index и megamarket.
Тесты
pnpm test # typecheck (включая тесты) + прогон
Тесты проверяют внешнее поведение через швы: инструменты MCP как чёрные ящики (поднимается
настоящий McpServer на in-memory транспорте), загрузку статического каталога (включая
деградацию товара без богатой детали), сроки доставки, реестр скиллов и выбор транспорта.
Рендер iframe проверяется вручную (см. ниже) — в CI его нет.
Тесты тайпчекаются вместе с кодом: гоняет их tsx, который типы не проверяет, поэтому
раньше tsc молча зеленел на тестах, ссылающихся на удалённые модули. Сборка идёт отдельным
конфигом (tsconfig.build.json), чтобы тесты не попадали в dist/.
Запуск / регистрация в Claude Desktop
Сервер говорит по MCP через stdio: Claude Desktop запускает его как дочерний процесс.
После pnpm build пропишите его в конфиг Claude Desktop.
Расположение файла конфигурации:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
Добавьте (замените путь на абсолютный путь к этому репозиторию):
{
"mcpServers": {
"mcp-app-proxyfier": {
"command": "node",
"args": ["/absolute/path/to/mcp-app-proxyfier/packages/server/dist/index.js"]
}
}
}
Затем полностью закройте и заново откройте Claude Desktop.
Ручная проверка рендера (главный риск демо)
Главный риск — баг рендера iframe на хосте (ext-apps #671): клиент согласует UI-возможность и тянет ресурс, но не рисует iframe. Поэтому его проверяют глазами на боевой сборке.
В чате Claude Desktop попросите модель вызвать инструмент ping (например, «вызови
инструмент ping с echo hello»). Убедитесь визуально:
- В чате нарисован интерактивный iframe (карточка с заголовком «MCP App Proxyfier»), а не только текстовый результат.
- Карточка показывает
message: pong,echo: helloи таймстамп — то естьstructuredContentинструмента дошёл до UI через мост.
Если виден только текст и iframe не рисуется — баг #671 воспроизведён: зафиксируйте версию Claude Desktop и держите наготове запасной текстовый сценарий для демо.
Абстракция транспорта
Сервер не зависит от транспорта. Инструменты и ресурсы регистрируются на McpServer без
знания о канале. Транспорт выбирается за швом ServerTransportProvider
(packages/server/src/transport/): stdio (по умолчанию) и http (Streamable HTTP). Оба
провайдера регистрируют ровно те же инструменты и ui:// ресурсы — добавление HTTP не
потребовало правок кода инструментов или ресурсов.
Транспорт выбирается флагом или переменной окружения (флаг приоритетнее):
| Параметр | Флаг | Env | По умолчанию |
|---|---|---|---|
| Транспорт | --transport stdio|http |
MCP_TRANSPORT |
stdio |
| Интерфейс прослушивания | --host |
MCP_HTTP_HOST |
127.0.0.1 |
| Порт | --port |
MCP_HTTP_PORT |
3000 |
| Путь эндпоинта | --path |
MCP_HTTP_PATH |
/mcp |
| Bearer-токен | --token |
MCP_HTTP_TOKEN |
(выкл.) |
Флаги понимают обе формы: --port 3000 и --port=3000.
Удалённый коннектор (Streamable HTTP)
Для демо, где владелец подключает коннектор сам (custom connector в claude.ai), а не Claude Desktop запускает его локально. Это альтернативный канал к тому же серверу — stdio-демо он не блокирует.
-
Соберите и запустите сервер по HTTP (слушает на
127.0.0.1:3000/mcp). Туннель делает порт публичным, поэтому задайтеMCP_HTTP_TOKEN— запросы безAuthorization: Bearer <token>отклоняются с401:pnpm build MCP_TRANSPORT=http MCP_HTTP_TOKEN="$(openssl rand -hex 16)" pnpm start # токен выкл. (только локально, без туннеля): pnpm start -- --transport http -
Откройте публичный HTTPS-туннель к этому локальному порту:
cloudflared tunnel --url http://127.0.0.1:3000 # → печатает https://<random>.trycloudflare.com # альтернатива ngrok: # ngrok http 3000 → https://<random>.ngrok-free.appURL коннектора — это origin туннеля плюс путь эндпоинта, например
https://<random>.trycloudflare.com/mcp. -
В claude.ai → Settings → Connectors → Add custom connector вставьте этот URL (и Bearer-токен в поле авторизации коннектора, если вы его задали). Claude инициализирует сессию Streamable HTTP и показывает те же инструменты и
ui://приложения, что и stdio.
Ручная проверка (рендер iframe на хосте, ext-apps #671). Как и для stdio, убедитесь визуально, что результат инструмента рисует интерактивный iframe в чате claude.ai, а не только текст. Баг рендера #671 — клиентский и не связан с транспортом, но его нужно перепроверить на claude.ai: сборка хоста отличается от Claude Desktop.
Замечания:
- Один запущенный процесс держит одну сессию Streamable HTTP — одного докладчика за туннелем.
- Реконнект = перезапуск. При чистом отключении claude.ai шлёт завершение сессии и
повторное подключение работает; после грязного обрыва (туннель умер) проще всего
Ctrl-Cи заново--transport http, если коннектор потерял сессию. - Прослушивание остаётся на localhost намеренно; не слушайте
0.0.0.0— доступ к серверу только через туннель. Защита от DNS-rebinding выключена намеренно (host туннеля динамический); доступ охраняет Bearer-токенMCP_HTTP_TOKEN. - После демо погасите туннель — URL является секретом.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.