MCP-сервер для рабочага журнала з пошукам у 190 радках TypeScript
Невялікі MCP-сервер па-над Markdown-файламі: чатыры інструменты пад намеры, апісанні ў стылі промптаў, дэма-кліент праз stdio, які адначасова служыць тэстам, і рэальныя пасткі.
Крыніцы: I Built an MCP Server That Keeps My Work Journal — Here’s Everything I Learned
Сервер протакола Model Context, напісаны ў фармаце TypeScript і складаюцыся з апошніх 190 радкоў, можа ператворыць папку з файламі у фармаце Markdown у пошукавы юмарнал працы, які можа чытаць і пісаць будзь-які дапаможнік AI. Гэты прыклад таксама ўтварае хорашы першы праект на базе MCP і паказвае, як прадстаўіць сервер без вікорыстання рэдагувальніка.
Чаму юмарнал?
Звычкі ўваходзіць даныя ў юмарнал ўсёлякі дзень рэдка калі трываюць больш за тыдзень. У тым часе дапаможнік AI, які вылечыў проблемы з паралельным выконанням задач, спільна напісаў палову тэстаў і стаў свідкам кожнай перэработкі коду, ужо знае, што адбылася працоўнага дня. Дысцыпліна ніколі не была справжнім пераканаў. Лог-даныя зберагаюцца не там, дзе знаходзіцца сама праца.
Зменіце падчынню: узамест таго, каб чалавек падпіліваў ўсё ў журнал, дадзіце асістэнту інструменты для яго ведчы. 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
Няма базы дадзеных. Файлы можна пераглядзець, зберагчыць і рэдагаваць вручную, і такі выбар прыносіць плоды пазней.
Стварэнне сервера
Офіцыйны SDK для TypeScript (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/— гэта атлас аўдыту, якога жадная база дадзеных не дае за замовчаннем. - LLMs чырпаюць Markdown дужа добра. Тое, што пішае модэль, падтрымліваецца з тым, што збераглена, без несувярэннасці серыялізацыі.
Цяй урок працюе і за межамі журналаў: калі і людзі, і LLM-ы выкарыстоўваюць вашы даны, яны павінны храніцца у фармаце, які і тыя, і іншыя ведаюць.
Запуск
Клонавайце, ствараюце і запускайце дэман:
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: кожны прынцып практыкуецца, рэзультаты вікорыстоўваюцца ў практыцы ўсё час, а складнасць застаецца настаткі чаму, каб можна было ўсё цяперашняе цалкам зрозумець.