Skip to main content
Glama

☕ MCP Coffee — учебный проект по Model Context Protocol

CI

Telegram-бот кофейни, который принимает заказы в свободной форме («два капучино и латте»), записывает их в Excel и сообщает время готовности. Главная цель проекта — разобраться, как устроен MCP изнутри: и сервер, и клиент написаны вручную, tool use реализован ручной петлёй поверх OpenAI, без готовых агентских врапперов.

Что демонстрирует проект

  • MCP-сервер на TypeScript — три инструмента, транспорт Streamable HTTP, сессии по заголовку mcp-session-id.

  • MCP-клиент — подключение, tools/list, tools/call.

  • Ручная петля tool use — модель сама извлекает структуру заказа из текста; мы перехватываем вызовы инструментов, исполняем их через MCP и возвращаем результат в диалог, пока модель не ответит текстом.

  • Разделение слоёв — хранение (Excel) отделено от бизнес-логики (инструменты) и от «мозга» (бот).

Related MCP server: cafe-mcp-server

Архитектура

Система состоит из трёх независимых частей:

Часть

Файл

Ответственность

MCP-сервер

src/index.ts

3 инструмента + Express/Streamable HTTP на :3000/mcp. Единственный, кто трогает Excel.

Слой хранения

src/excel.ts

Чтение menu.xlsx, запись/чтение orders.xlsx. Не знает бизнес-правил.

MCP-клиент

src/mcpClient.ts

connect / listTools / callTool.

Бот

src/bot.ts

Telegram + OpenAI + ручная петля tool use.

Ключевой принцип: сервер не знает про OpenAI, а OpenAI не знает про Excel. Бот посередине — переводчик между ними.

Поток данных

Пользователь в Telegram: "два капучино"
        │
        ▼
   [bot.ts] получает текст
        │
        ▼
   [bot.ts] → OpenAI: сообщение + список инструментов (function calling)
        │
        ▼
   OpenAI решает вызвать create_order({items:[{drink:"капучино",qty:2}]})
        │
        ▼
   [bot.ts] перехватывает вызов          ← РУЧНАЯ ПЕТЛЯ
        │
        ▼
   [mcpClient] → [index.ts] tools/call create_order(...)
        │
        ▼
   [index.ts] читает menu.xlsx (время), пишет строку в orders.xlsx
        │
        ▼
   результат {id, totalMinutes} → обратно в OpenAI
        │
        ▼
   OpenAI формулирует ответ: "Заказ №7 принят, готовность ~8 мин"
        │
        ▼
   [bot.ts] → Telegram

Инструменты MCP

Инструмент

Аргументы

Возвращает

get_menu

—

список напитков {drink, price, minutes, composition}

search_menu

query

напитки, у которых ключевое слово есть в составе/названии

create_order

items: [{drink, qty}]

{id, totalMinutes}

get_order_status

orderId

{status, minutesLeft, items}

Описания инструментов и схемы аргументов — на английском: это контракт для модели, по которому она выбирает инструмент и заполняет аргументы.

Технологии

TypeScript · @modelcontextprotocol/sdk · Express · OpenAI SDK · Telegraf · ExcelJS · zod · Node.js 24

Запуск

npm install
cp .env.example .env   # впиши OPENAI_API_KEY и TELEGRAM_BOT_TOKEN

.env:

OPENAI_API_KEY=sk-...
TELEGRAM_BOT_TOKEN=123456:ABC-...
OPENAI_MODEL=gpt-4o-mini
MCP_URL=http://localhost:3000/mcp

Два терминала:

npm run dev       # 1-й: MCP-сервер на :3000
npm run dev:bot   # 2-й: Telegram-бот

Дальше пиши боту: «два капучино и латте» → получишь номер и время; «когда готов заказ 3?» → остаток минут до готовности.

Команды

Команда

Что делает

npm run dev

MCP-сервер (tsx watch)

npm run dev:bot

Telegram-бот (tsx watch)

npm run build

сборка в dist/

npm start

запуск собранного сервера

npm test

юнит-тесты (встроенный node:test)

Структура

src/
├── index.ts          точка входа: MCP-сервер (инструменты + HTTP-транспорт)
├── bot.ts            точка входа: Telegram + OpenAI + ручная петля tool use
├── config.ts         настройки в одном месте (модель, порт, лимиты)
├── domain/           ЧИСТАЯ логика без I/O (тестируется без запуска сервера)
│   ├── search.ts       поиск по составу
│   ├── orders.ts       расчёт заказа: проверка меню, время
│   └── history.ts      обрезка окна контекста
├── infra/            адаптеры к внешнему миру
│   ├── excel.ts        слой хранения (ExcelJS)
│   └── mcpClient.ts    MCP-клиент (с авто-переподключением)
└── tests/            юнит-тесты к чистой логике
data/
└── menu.xlsx         меню (напиток | цена | время_приготовления_мин | состав)
                      orders.xlsx создаётся при первом заказе

Архитектура: точки входа наверху, чистое ядро (domain/) отделено от адаптеров к внешнему миру (infra/). Благодаря этому логику можно тестировать, не запуская сервер и бота.

Принятые решения и почему

Этот раздел — самое важное в проекте: он показывает понимание, а не только код.

  • Ручная петля tool use вместо готового агента. Цель — увидеть механику: как модель возвращает tool_calls, как результат с ролью tool и tool_call_id привязывается к конкретному вызову, как цикл повторяется. Это понимание дороже, чем «подключил библиотеку».

  • Поиск по составу — это retrieval, а не исправление опечаток. Нормализацию текста («КАПУЧино», «капуччино») делает сама LLM — дублировать её на сервере бессмысленно. А вот состав напитков лежит в Excel, и в контексте модели его нет: по названию «Розовая волна» не угадать, что там арбузный сироп. Поэтому search_menu оправдан: LLM извлекает ключевое слово («арбуз»), а сервер находит напитки по данным, которых у модели нет. Это тот же паттерн, что и поиск по большому каталогу, который не помещается в окно контекста.

  • Расчёт времени — в инструменте, не в слое хранения. Чтобы посчитать общее время, нужно сопоставить заказ с меню — это та же операция, что и валидация «существует ли напиток». Логично держать в одном месте. excel.ts остаётся тупым хранилищем: поменяем формулу — слой хранения не трогаем.

  • Общее время = сумма время × количество. Сознательное упрощение для модели «один бариста по очереди». Легко заменить на максимум (параллельная готовка) — правка в одном месте, в обработчике create_order.

  • Описания инструментов на английском, общение — на русском. Описания и .describe() — контракт для модели (выбор инструмента, заполнение аргументов): на английском надёжнее для function calling и экономнее по токенам. Тексты ошибок — на русском: они возвращаются модели как данные, и она пересказывает их пользователю.

  • Язык ответа задан в системном промпте. gpt-4o-mini иногда вставляет иноязычные слова. Явное требование «отвечай только по-русски» в системном промпте убирает большинство таких сбоев; при необходимости можно дополнительно понизить temperature.

  • Сессии MCP по mcp-session-id. Один разговор клиента с сервером = одна сессия со своим транспортом; так несколько клиентов не мешают друг другу.

  • Автопереподключение к MCP. Сессии живут в памяти сервера: после его рестарта mcp-session-id бота протухает, и сервер отвечает 400. Чтобы бот не падал на каждом сообщении до перезапуска, McpClient при ошибке запроса один раз переподключается (новая сессия) и повторяет вызов.

  • Управление окном контекста — обрезка по границам ходов. История растёт с каждым сообщением и упёрлась бы в лимит токенов. Обрезаем до MAX_TURNS последних ходов, но не как попало: у tool-use есть инвариант — assistant с tool_calls обязан сопровождаться tool-ответами с теми же tool_call_id. Поэтому окно всегда начинается с сообщения user и системное сообщение сохраняется — иначе «осиротевший» tool дал бы ошибку 400.

Известные ограничения

  • История диалога хранится в памяти процесса — перезапуск бота её обнуляет.

  • Обрезка контекста — по числу ходов (MAX_TURNS), а не по токенам. Точнее было бы считать токены (tiktoken), но для учебного проекта порога по ходам достаточно.

  • Юнит-тесты на чистую логику гоняются в CI (GitHub Actions) на каждый push.

Related MCP Connectors

Related MCP Servers

  • F
    license
    A
    quality
    B
    maintenance
    MCP server that recommends coffee based on preferences (mood, milk, caffeine, temperature) from a static menu; includes tools for listing menu, recommending, and explaining recommendations.
    3
    -
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables natural language interaction with a coffee shop's inventory and order system, allowing users to check menu items, place orders, and track order status via MCP tools.
    -
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables interaction with a Web Shop backend API through MCP, allowing users to browse products, create and view orders, and admins to manage all orders and users.
    -