Створіть сервер MCP для робочого щоденника з пошуком у 190 рядках TypeScript
Невеликий сервер MCP для файлів Markdown: чотири інструменти на основі намірів, описи у стилі запитів, демонстраційний клієнт stdio, який виконує також функцію тесту, а також справжні підводні камені.
Джерела: I Built an MCP Server That Keeps My Work Journal — Here’s Everything I Learned
Сервер протоколу Model Context Protocol, написаний приблизно з 190 рядків TypeScript, може перетворити папку з файлами Markdown на пошуковий щоденник роботи, який може читати та писати будь-який штучний інтелект-асистент. Це також чудовий перший проект типу MCP, який демонструє, як запустити сервер без редактора.
Чому саме щоденник?
Звички щоденного ведення записів рідко тривають більше тижня. Тим часом штучний інтелект-асистент, який усунув проблеми з одночасним виконанням операцій, співписав половину тестів та стежив за кожною оптимізацією, вже знає, що відбувалося протягом дня. Дисципліна ніколи не була справжньою перешкодою. Журнал записів знаходиться десь окремо від робочого простору.
Змінімо підхід: замість того, щоб людина ведла щоденник, надаймо асистенту інструменти для цього. MCP — стандарт, який дозволяє клієнтам LLM знаходити та викликати зовнішні інструменти, ідеально підходить для цього. Сервер dev-diary, який обгортає звичайну директорію з файлами Markdown, охоплює всі основні аспекти (інструменти, схеми, передача даних через stdio, безстанове зберігання) менш ніж у 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, зберігати та редагувати вручну, і цей вибір принесе користь пізніше.
Створення сервера
Основою є офіційний TypeScript SDK (v2, який реалізує специфікацію 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.'
Другий елемент вказує моделі, коли потрібно скористатися інструментом. Його умова „використовувати це завжди“, коли йдеться про остаточне виправлення проблемного тесту, перетворює таку спонтанну примітку на виклик інструменту без явного наказу. Описи параметрів працюють аналогічно: підказка на кшталт Опційні мітки, наприклад ["bugfix","api"] демонструє бажану форму за допомогою прикладу.
Інструменти відповідають запитанням, а не операціям
Перший чернеток із інструментами CRUD (add_entry, update_entry, delete_entry) має неправильну структуру. Ніхто не просить щоденник оновити запис номер 47 — вони запитують, що сталося з багом WebSocket минулого тижня. Тож остаточна версія відповідає поставленій меті: search_diary знаходить потрібну інформацію, daily_summary підсумовує день, stats надає мотиваційний огляд, а log_work виконує функцію єдиного автора. Ці чотири інструменти не перетинаються між собою та кожен відповідає на конкретне запитання розробника. Основне правило: один інструмент на одне запитання користувача, а не один на кожну операцію з даними.
Демонстрація без редактора
Складнішою є проблема демонстрації роботи сервера MCP. Підключення Claude Desktop та зроблення скріншота чату не є відтворюваним доказом, залежить від закритого додатку та не може працювати в середовищі CI чи на комп’ютері користувача.
Тиха сила специфікації допомагає тут: MCP — це просто клієнт та сервер через stdio. Ніщо не заважає другій невеликій програмі під’єднатися так, як це робить редактор, та керувати серверною частиною від початку до кінця. Її імпорти:
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/— це слід аудиту, якого жодна база даних за замовчуванням не надає. - LLM дуже добре працюють з 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: кожна концепція розглядається, результати використовуються щодня, а обсяг матеріалу залишається достатньо невеликим для повного розуміння.