Создание сервера MCP для рабочего дневника с возможностью поиска из 190 строк TypeScript
Небольшой сервер MCP для работы с файлами Markdown: четыре инструмента на основе намерений, описания в стиле промптов, демонстрационный клиент с вводом-выводом, служащий одновременно тестом, а также реальные подводные камни.
Источники: I Built an MCP Server That Keeps My Work Journal — Here’s Everything I Learned
Сервер протокола Model Context Protocol, написанный на TypeScript примерно из 190 строк, может превратить папку с файлами Markdown в поисковый рабочий дневник, который может читать и писать любой ИИ-ассистент. Это также отличный первый проект на основе MCP, демонстрирующий, как запустить сервер без использования редактора.
Почему дневник?
Привычка вести ежедневные записи редко сохраняется дольше недели. Тем временем ИИ-ассистент, который устранил проблему соревновательного доступа, помог написать половину тестов и наблюдал за каждой переработкой кода, уже знает, что происходило в течение дня. Дисциплина никогда не была настоящей преградой. Журнал записей хранится где-то отдельно от рабочих файлов.
Измените подход: вместо того чтобы заставлять человека вести дневник, дайте ассистенту инструменты для этого. MCP — стандарт, позволяющий клиентам LLM находить и вызывать внешние инструменты, — идеально подходит для этой цели. Сервер dev-diary, представляющий собой обычную папку с файлами Markdown, охватывает все основные аспекты (инструменты, схемы, передача данных через стандартный ввод-вывод, хранение без состояния) менее чем за 200 строк, что делает его подходящим инструментом для обучения.
Сам дневник не является главной целью — такие приложения уже существуют. Тема проста, неинтересна, но понятна всем, что идеально подходит для обучающего проекта. Основной вопрос заключается в том, может ли кто-либо создать сервер MCP для любой задачи. Да, причем протокол оказывается самой простой частью; сложнее решить, что именно нужно обернуть в такой сервер. Журнал тренировок, список книг для прочтения, внутренняя вики или умный дом могут использоваться через сервер MCP, если код может с ними работать — читать или записывать данные.
Четыре инструмента
| Инструмент | Назначение |
|---|---|
log_work | Добавляет запись с временной меткой и тегом в файл сегодняшнего дня |
search_diary | Поиск по всему тексту всех записей |
daily_summary | Все, что было зарегистрировано в выбранную дату |
stats | Общие показатели, серии записей и наиболее популярные теги |
Хранение данных выполнено в простом формате: по одному файлу Markdown на день с такими записями.
## 14:32 #bugfix #websocket
Fixed the race condition in the WebSocket broadcast queue
## 16:10 #testing
Wrote E2E test covering two-client sync
Базы данных нет. Файлы можно найти с помощью grep, сохранить и отредактировать вручную, и такой подход оправдывается позже.
Сборка сервера
Основой служит официальный SDK TypeScript (версия 2, реализующая спецификацию от 2026-07-28). Для установки требуются пакет сервера и Zod:
npm install @modelcontextprotocol/server zod
Импорты включают класс сервера, механизм передачи данных stdio и точку входа версии v4 библиотеки Zod:
import { McpServer } from '@modelcontextprotocol/server';
import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';
import * as z from 'zod/v4';
Объект сервера имеет имя и версию:
const server = new McpServer({ name: 'dev-diary', version: '1.0.0' });
Каждый инструмент регистрируется с именем, конфигурацией и обработчиком. Вот пример log_work:
server.registerTool(
'log_work',
{
description: 'Append a timestamped entry to the developer diary...',
inputSchema: z.object({
text: z.string().min(1),
tags: z.array(z.string()).optional(),
date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
}),
},
async ({ text, tags = [], date }) => {
// ...append to diary/YYYY-MM-DD.md...
return { content: [{ type: 'text', text: 'Logged.' }] };
},
);
В заключение — подключение через stdio:
await server.connect(new StdioServerTransport());
Это полноценный сервер. Его функционирование обеспечивают четыре ключевых элемента:
registerTool(name, config, handler)формирует каталог инструментов.inputSchema— это схема Zod; SDK преобразует её в JSON Schema для клиента и проверяет аргументы перед запуском обработчика.- Значение, возвращаемое в формате
{ content: [{ type: 'text', text: '...' }] }, и является результатом, который читает LLM. - Механизм передачи данных
StdioServerTransport()позволяет клиенту запускать процесс и взаимодействовать по протоколу JSON-RPC через stdin и stdout. Для локального использования не требуются порты и аутентификация.
Описания — это инструкции
Это самое значительное изменение в подходе. Описание инструмента — это не документация API для людей, а инструкция, которую модель читает, чтобы определить, когда и как вызвать инструмент. Сравните с описанием в стиле документации:
// ❌ documentation-style
description: 'Appends an entry to the diary.'
с вариантом в стиле запроса:
// ✅ prompt-style
description: 'Append a timestamped entry to the developer diary for today.
Use this whenever the user says they finished/did/fixed something and
wants it recorded.'
Второй элемент указывает модели, когда следует воспользоваться инструментом. Пункт «использовать это в любом случае» превращает случайное замечание о наконец-то исправленном нестабильном тесте в вызов инструмента без явного приказа. Описания параметров работают аналогичным образом: пример вроде Опциональные метки, например, ["исправление бага","api"] показывает ожидаемый формат на практике.
Инструменты соответствуют вопросам, а не операциям
Первый черновик с инструментами CRUD (add_entry, update_entry, delete_entry) имеет неверную структуру. Никто не просит журнал обновить запись номер 47 — они спрашивают, что произошло с багом WebSocket на прошлой неделе. Поэтому окончательная версия соответствует замыслу: search_diary находит нужные данные, daily_summary дает краткий обзор дня, stats предоставляет статистику для мотивации, а log_work служит инструментом для записи. Эти четыре инструмента не перекрываются друг с другом и каждый отвечает на вопрос, который действительно возникает у разработчика. Основное правило: по одному инструменту на каждый вопрос пользователя, а не по одному на каждую операцию с данными.
Демонстрация без редактора
Более сложной задачей является показ работы сервера MCP. Подключение Claude Desktop и сделка скриншота чата не доказывают ничего воспроизводимого, зависят от закрытого приложения и не могут запускаться в среде CI или на компьютере читателя.
Здесь помогает скрытая прочность спецификации: MCP — это просто клиент и сервер через стандартный ввод-вывод. Ничто не мешает второй небольшой программе подключиться так же, как это делает редактор, и управлять серверной частью от начала до конца. Его импорты:
import { Client } from '@modelcontextprotocol/client';
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';
Он запускает сервер в качестве дочернего процесса:
const transport = new StdioClientTransport({
command: 'node',
args: ['dist/server.js'], // spawns the server as a child process
});
const client = new Client({ name: 'demo-client', version: '1.0.0' });
await client.connect(transport);
Затем он выполняет те же действия, что и настольный клиент в фоновом режиме: выводит список инструментов и вызывает их:
// Exactly what Claude Desktop does under the hood:
const { tools } = await client.listTools();
await client.callTool({ name: 'log_work', arguments: {
text: 'Fixed the race condition in the broadcast queue',
tags: ['bugfix', 'websocket'],
}});
Примерно через пятьдесят строк команда npm run demo выводит полную информацию о сессии протокола в качестве доказательства. В списке инструментов:
=== 1. listTools ===
• log_work — Append a timestamped entry to the developer diary...
• search_diary — Full-text search across every entry...
• daily_summary — Everything logged on a given date...
• stats — Totals, active days, streaks, top tags...
Три записи:
=== 2. log_work x3 ===
Logged to 2026-08-22.md at 19:05 (tags: bugfix, websocket)
...
А вот вид представления статистики:
=== 5. stats ===
📊 1 entries across 2 day(s)
🔥 Streak: 2 consecutive day(s)
🏷️ Top tags: #bugfix (1), #websocket (1)
Поскольку это скрипт, он одновременно служит тестом на интеграцию. После каждой изменения на сервере запускается демо-версия, и любые сбои в процессе подключения или изменения схемы приводят к явной ошибке. Демо-версия, документация и набор тестов объединяются в один продукт. Для визуального управления существует официальный инструмент Inspector, который запускается командой npx @modelcontextprotocol/inspector node dist/server.js и открывает веб-интерфейс для ручного использования инструментов. Совместное применение этих двух инструментов покрывает как процесс разработки, так и демонстрацию.
Важные моменты
- Использование await на верхнем уровне может нарушить сборку проекта. В документации SDK используется конструкция
await server.connect(...)в области модуля. Это работает для пакетов ESM ("type": "module"), но приводит к серьезной ошибке при компиляции TypeScript в формат CommonJS. Обертка старта в функциюmain()не требует дополнительных усилий и работает в любых средах. exactOptionalPropertyTypesпротиворечит схемам с необязательными полями. В строгом режиме TypeScript возникает ошибка при передачеtags: undefinedв полеz.string().optional(). Необходимо либо снизить строгость настроек, либо нормализовать аргументы на уровне обработчика.- Описания занимают ресурсы. Каждое описание инструмента сохраняется в модели во всех разговорах, где подключен сервер. Маркетинговый текст тратит токены и снижает читаемость полезных инструкций, поэтому лучше использовать краткий, прямой текст, сфокусированный на цели.
- Отсутствие состояния способствует эффективности. Сервер не сохраняет ничего в памяти и перечитывает Markdown при каждом запросе. Ручные изменения между запросами никогда не приводят к устаревшим данным, а два клиента в одной папке могут бесплатно делиться актуальной информацией. Откладывайте использование кэша для инструментов, основанных на файлах, до тех пор, пока профилирование не покажет его необходимость.
- Часовые пояса влияют даже в игрушках.
new Date().toISOString().slice(11,16)возвращает данные в формате UTC. Человек, находящийся в часовом поясе UTC-3 и ведущий записи в 18:00, увидит время 21:05. Проблему можно решить одной строкой кода, как только её заметят.
Настройка
В Claude Desktop, Cursor или любом другом клиенте, поддерживающем MCP, конфигурация (в формате claude_desktop_config.json) выглядит следующим образом:
{
"mcpServers": {
"dev-diary": {
"command": "node",
"args": ["/absolute/path/to/dev-diary-mcp/dist/server.js"],
"env": { "DIARY_DIR": "/home/you/journal" }
}
}
}
Укажите переменную DIARY_DIR на папку внутри репозитория dotfiles, и в качестве побочного эффекта будут формироваться сами версии дневника. Через неделю запрос вроде «подготовьте краткое описание моей недели из дневника, сгруппированное по тегам» приведёт к созданию отчёта о статусе, который никому не пришлось бы писать.
Почему именно Markdown
Наиболее обоснованный выбор заслуживает обоснования. SQLite предлагает более мощные запросы, но имеет непрозрачное хранение данных, а JSON легко парсится, но плохо справляется с вычислением различий. Markdown победил по четырём причинам:
- Человек — это клиент первого класса. Дневник хорошо читается в редакторе, с помощью grep или в логах git, без необходимости в дополнительных инструментах.
- Git обеспечивает резервное копирование и историю изменений. Команда
git log diary/создаёт запись всех изменений, которую ни одна база данных по умолчанию не предоставляет. - Нейросети отлично справляются с Markdown. То, что пишет модель, полностью совпадает с тем, что хранится, без каких-либо несоответствий при сериализации.
Этот урок актуален не только для журналов: когда и люди, и большие языковые модели используют ваши данные, храните их в формате, понятном обоим.
Запуск
Скопируйте, соберите и запустите демо-версию:
git clone https://github.com/rogeriolaa/dev-diary-mcp
cd dev-diary-mcp && npm install && npm run build && npm run demo
Затем добавьте сервер в конфигурацию MCP редактора и позвольте ассистенту начать ведение логов. Сам сервер, демонстрационный клиент и документация README вместе составляют примерно 400 строк, включая конфигурацию.
Подобные небольшие серверы — эффективный способ изучения MCP: здесь рассматриваются все концепции, результаты их использования видны ежедневно, а объем материала остается достаточно небольшим для полного понимания.