1

Pitch: Criei um servidor MCP que mantém meu diário de trabalho — e tudo que aprendi no caminho

Um servidor Model Context Protocol em ~190 linhas de TypeScript que transforma uma pasta de Markdown em um diário de trabalho que qualquer IA pode ler e escrever. E o mais importante: qualquer pessoa consegue construir um servidor MCP para qualquer coisa.


Seu chat com a IA de um lado, o arquivo Markdown do outro — a imagem mostra os dois

Sinceridade: quanto tempo durou sua última tentativa de "anotar o que fiz hoje"? Uma semana? Três dias?

Mas pensa comigo — em 2026, outra coisa já sabe o que você fez o dia inteiro: seu assistente de IA. Foi ele que te ajudou a debugar aquele race condition, escreveu metade dos testes junto com você e acompanhou cada refactor. O problema do diário nunca foi disciplina. É que o diário fica num lugar separado do trabalho.

Então inverti a lógica: em vez de eu alimentar o diário, por que não dar ferramentas pro meu assistente manter um?

O encaixe natural é o MCP (Model Context Protocol) — o padrão que permite clientes de LLM descobrir e chamar ferramentas externas. Construí o dev-diary-mcp: um servidor que transforma uma pasta simples de arquivos Markdown em um diário pesquisável. Acabou sendo o projeto de tamanho ideal para aprender MCP, porque passa por todos os conceitos centrais (tools, schemas, transporte stdio, armazenamento sem estado) sem passar de 200 linhas.

Antes de qualquer coisa, deixa eu ser claro: o diário não é o ponto. Existem apps de diário aos montes; você não precisa que eu construa outro. Escolhi esse tema justamente por ser pequeno, chato e entendível por qualquer pessoa — o formato ideal para um projeto de estudo. O objetivo real era responder uma pergunta que eu fazia pra mim mesmo: "servidores MCP parecem mágica. Será que qualquer pessoa consegue construir um pra qualquer coisa?"

A resposta é sim — e quando você ver a receita completa, vai perceber que a parte difícil nunca foi o protocolo. Difícil é escolher o que colocar atrás dele. Seu registro de academia, sua lista de leitura, a wiki interna da empresa, sua casa inteligente — se dá pra ler ou escrever com código, dá pra ter um servidor MCP em cima disso até sexta-feira.

O que ele faz

O assistente ganha quatro ferramentas:

FerramentaO que faz
log_workAdiciona uma entrada com hora e tags no arquivo do dia
search_diaryBusca textual em todas as entradas
daily_summaryTudo o que foi registrado numa data
statsTotais, dias seguidos, tags mais usadas

O formato de armazenamento é propositalmente sem graça. Um arquivo por dia:

## 14:32 #bugfix #websocket
Corrigi o race condition na fila de broadcast do WebSocket

## 16:10 #testing
Escrevi teste E2E cobrindo sincronização entre dois clientes

Só isso. Sem banco de dados. Dá pra dar grep, commitar, editar na mão. Essa escolha importou mais do que eu esperava — já chego lá.

Como construir o seu (a receita completa)

1. O esqueleto é pequeno de verdade

Com o SDK oficial TypeScript (v2, especificação 2026-07-28):

npm install @modelcontextprotocol/server 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' });

server.registerTool(
  'log_work',
  {
    description: 'Adiciona uma entrada com data e hora ao diário...',
    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 }) => {
    // ...adiciona em diary/YYYY-MM-DD.md...
    return { content: [{ type: 'text', text: 'Registrado.' }] };
  },
);

await server.connect(new StdioServerTransport());

Isso já é um servidor MCP completo. Quatro conceitos resolvem tudo:

  1. registerTool(nome, config, handler) — o catálogo de ferramentas
  2. inputSchema — um schema Zod; o SDK converte para JSON Schema para o cliente e valida os argumentos antes do seu código rodar
  3. Valor de retorno{ content: [{ type: 'text', text: '...' }] }; é isso que a IA lê como resultado da ferramenta
  4. TransporteStdioServerTransport() significa que o editor abre seu processo como filho e conversa via JSON-RPC pelo stdin/stdout. Sem porta, sem autenticação no uso local

2. As descrições são prompt, não documentação

Essa foi a maior mudança de mentalidade. A descrição da sua tool não é documentação para humano — é instrução que a IA lê para decidir quando e como usar a ferramenta. Compare:

// ❌ estilo documentação
description: 'Adiciona uma entrada ao diário.'

// ✅ estilo prompt
description: 'Adiciona uma entrada com data e hora ao diário de trabalho.
Use sempre que o usuário disser que terminou/consertou/algo e quiser
registrar.'

A segunda versão diz ao modelo quando buscar a ferramenta. Aquela frase "use sempre que..." faz trabalho de verdade — ela transforma intenção do usuário ("consegui consertar aquele teste instável") em chamada de ferramenta sem ninguém digitar comando nenhum.

O mesmo vale para as descrições dos parâmetros: tags: 'Rótulos opcionais, ex.: ["bugfix","api"]' ensina o formato esperado com exemplo.

3. Desenhe ferramentas em volta de perguntas, não de tabelas

Minha primeira versão tinha ferramentas de CRUD (add_entry, update_entry, delete_entry). Formato errado. Ninguém pede pro diário "atualizar a entrada 47" — a pessoa pergunta "o que eu fiz sobre o bug do WebSocket semana passada?"

Então o conjunto final virou intenções: search_diary (achar coisas), daily_summary (resumo do dia), stats (a parte motivacional) e uma única ferramenta de escrita, log_work. Quatro ferramentas, sem repetir função, cada uma respondendo uma pergunta real de dev.

Regra que fixei pra mim: uma ferramenta por pergunta do usuário, não uma por operação de dados.

O problema da demo (e a solução elegante)

Quase matou o projeto: como demonstrar um servidor MCP?

A resposta óbvia seria "conecta no Claude Desktop e tira print do chat" — o que não prova nada de reproduzível, depende de app fechado e não roda em CI nem na máquina de quem lê.

Aí lembrei de um detalhe simples da especificação: MCP é só cliente/servidor via stdio. Nada me impede de escrever um segundo programinha que conecta no meu servidor igual um editor faria e testa tudo de ponta a ponta:

import { Client } from '@modelcontextprotocol/client';
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';

const transport = new StdioClientTransport({
  command: 'node',
  args: ['dist/server.js'],   // abre o servidor como processo filho
});
const client = new Client({ name: 'demo-client', version: '1.0.0' });
await client.connect(transport);

// Exatamente o que o Claude Desktop faz por baixo:
const { tools } = await client.listTools();
await client.callTool({ name: 'log_work', arguments: {
  text: 'Corrigi o race condition na fila de broadcast',
  tags: ['bugfix', 'websocket'],
}});

Cinquenta linhas depois, npm run demo produz isto — uma sessão de protocolo completa como prova:

=== 1. listTools ===
 • log_work — Adiciona entrada com data e hora ao diário...
 • search_diary — Busca textual em todas as entradas...
 • daily_summary — Tudo de uma data específica...
 • stats — Totais, dias seguidos, tags mais usadas...

=== 2. log_work x3 ===
Logged to 2026-08-22.md at 19:05 (tags: bugfix, websocket)
...

=== 5. stats ===
📊 1 entradas em 2 dia(s)
🔥 Streak: 2 dia(s) consecutivos
🏷️ Tags mais usadas: #bugfix (1), #websocket (1)

E como é script, serve também de teste de integração. Toda mudança no servidor, rodo a demo; se o handshake quebrar ou algum schema sair do esperado, o erro aparece na hora. Demo, documentação e teste num arquivo só.

Se preferir algo visual, o Inspector oficial (npx @modelcontextprotocol/inspector node dist/server.js) abre uma interface web pra brincar com as tools na mão. Os dois juntos cobrem desenvolvimento e demonstração.

Coisas que os tutoriais não contam

Cuidado com top-level await. O README do SDK mostra await server.connect(...) solto no escopo do módulo — funciona se seu package for ESM ("type": "module"), mas dá erro se o TypeScript compilar para CommonJS. Envolver a inicialização numa função main() não custa nada e funciona nos dois casos.

exactOptionalPropertyTypes não convive bem com schemas opcionais. Com TS estrito, passar tags: undefined num campo z.string().optional() dá erro. Ou relaxa a flag, ou normaliza os argumentos no começo do handler. Perdi dez minutos confuso com isso.

As descrições entram no contexto. Toda descrição de tool vai para o modelo em toda conversa em que o servidor estiver conectado. Textão de marketing desperdiça tokens e atrapalha as instruções úteis. Curto, direto, focado em quando usar.

Servidor sem estado é uma vantagem. Meu servidor não guarda nada em memória — relê os arquivos Markdown a cada chamada. Consequência: posso editar arquivos na mão entre chamadas e nunca vejo dado velho, e dois clientes apontando para a mesma pasta dividem a mesma fonte de verdade. Resista à tentação de colocar cache na frente de tools baseadas em arquivo até medir e ver que precisa.

Fuso horário é armadilha até em projeto de brinquedo. new Date().toISOString().slice(11,16) devolve UTC — tranquilo até alguém no UTC-3 registrar às 18h e ver "21:05". Uma linha resolve, mas só depois que você percebe.

Conectando de verdade

No Claude Desktop / Cursor / qualquer cliente MCP (estilo claude_desktop_config.json):

{
  "mcpServers": {
    "dev-diary": {
      "command": "node",
      "args": ["/caminho/absoluto/dev-diary-mcp/dist/server.js"],
      "env": { "DIARY_DIR": "/home/voce/journal" }
    }
  }
}

Aponte o DIARY_DIR para uma pasta dentro do seu repositório de dotfiles e seu diário de trabalho já vai versionado no git de graça. Depois de uma semana usando, peça: "resume minha semana pelo diário, agrupado por tag" — e receba um relatório que ninguém precisou escrever.

Por que Markdown-como-banco venceu

Quero defender a escolha mais polêmica. As alternativas eram SQLite (melhores queries, mas os dados ficam presos lá dentro) ou JSON (fácil de parsear, diff horrível). Os arquivos Markdown ganharam porque:

  1. O humano é cliente de primeira classe. Leio meu diário no editor, no grep, no git log — sem ferramenta nenhuma.
  2. Git vira backup + histórico de graça. git log diary/ é trilha de auditoria que nenhum banco te dá de padrão.
  3. LLMs são excelentes com Markdown. Zero incompatibilidade entre o que o modelo escreve e o que fica guardado.
  4. Quando quebra, dá pra ver. Uma linha corrompida fica visível e dá pra consertar na mão; página de SQLite corrompida, não.

A lição generaliza além de diários: quando humanos e IAs são clientes dos seus dados, guarde no formato que os dois já falam.

Roda aí

git clone https://github.com/rogeriolaa/dev-diary-mcp
cd dev-diary-mcp && npm install && npm run build && npm run demo

Depois conecta no MCP config do seu editor e deixa o assistente começar a anotar. O projeto todo — servidor, cliente de demo, README — tem umas 400 linhas contando configuração.

Construir servidores MCP pequenos assim é, na minha opinião, o jeito de maior retorno pra aprender o protocolo: você passa por todos os conceitos, entrega algo que vai usar todo dia, e a superfície é pequena o suficiente pra entender por completo.

E você: qual seria a sua automação de cinco linhas por dia? Um servidor MCP pra sua lista de leitura? Pro treino da academia? Pros relatórios da daily? Conta aqui nos comentários — e se construir, manda o link 🚀

Carregando publicação patrocinada...