mcp-coffee
Enables coffee shop order management via MCP, including menu lookup, order creation, and status tracking.
README
☕ MCP Coffee — учебный проект по Model Context Protocol
Telegram-бот кофейни, который принимает заказы в свободной форме («два капучино и латте»), записывает их в Excel и сообщает время готовности. Главная цель проекта — разобраться, как устроен MCP изнутри: и сервер, и клиент написаны вручную, tool use реализован ручной петлёй поверх OpenAI, без готовых агентских врапперов.
Что демонстрирует проект
- MCP-сервер на TypeScript — три инструмента, транспорт Streamable HTTP,
сессии по заголовку
mcp-session-id. - MCP-клиент — подключение,
tools/list,tools/call. - Ручная петля tool use — модель сама извлекает структуру заказа из текста; мы перехватываем вызовы инструментов, исполняем их через MCP и возвращаем результат в диалог, пока модель не ответит текстом.
- Разделение слоёв — хранение (Excel) отделено от бизнес-логики (инструменты) и от «мозга» (бот).
Архитектура
Система состоит из трёх независимых частей:
| Часть | Файл | Ответственность |
|---|---|---|
| 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 |
запуск собранного сервера |
Структура
src/
├── index.ts MCP-сервер: инструменты + HTTP-транспорт
├── excel.ts слой хранения (ExcelJS)
├── mcpClient.ts MCP-клиент
└── bot.ts Telegram + OpenAI + ручная петля tool use
data/
└── menu.xlsx меню (напиток | цена | время_приготовления_мин | состав)
orders.xlsx создаётся при первом заказе
Принятые решения и почему
Этот раздел — самое важное в проекте: он показывает понимание, а не только код.
-
Ручная петля 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. Один разговор клиента с сервером = одна сессия со своим транспортом; так несколько клиентов не мешают друг другу. -
Управление окном контекста — обрезка по границам ходов. История растёт с каждым сообщением и упёрлась бы в лимит токенов. Обрезаем до
MAX_TURNSпоследних ходов, но не как попало: у tool-use есть инвариант —assistantсtool_callsобязан сопровождатьсяtool-ответами с теми жеtool_call_id. Поэтому окно всегда начинается с сообщенияuserи системное сообщение сохраняется — иначе «осиротевший»toolдал бы ошибку 400.
Известные ограничения
- История диалога хранится в памяти процесса — перезапуск бота её обнуляет.
- Обрезка контекста — по числу ходов (
MAX_TURNS), а не по токенам. Точнее было бы считать токены (tiktoken), но для учебного проекта порога по ходам достаточно. - Нет автотестов и CI.
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.
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.
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.
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.