Skip to main content
Glama

Task CLI + MCP

Aplicação de linha de comando para gerenciamento de tarefas, estendida com um servidor MCP (Model Context Protocol) que expõe operações da aplicação para clientes de IA através de tools estruturadas, evitando chamadas de shell e parsing manual de saída.

Este projeto foi desenvolvido como um exercício de arquitetura, integração com IA e desenvolvimento orientado por especificação. O foco de engenharia está na separação entre domínio e interfaces: a lógica de negócio existe uma única vez e é consumida tanto pela CLI quanto pela camada MCP.


Overview

O projeto começou como uma CLI de um comando, com três operações e persistência em arquivo:

  • adicionar tarefas — cria uma tarefa pendente, atribui um id inteiro incremental (nunca reutilizado) e confirma o id;

  • listar tarefas — mostra id, status (pendente/concluída) e descrição; informa explicitamente quando não há tarefas;

  • concluir tarefas — marca uma tarefa pendente como concluída; se já estiver concluída, apenas informa e encerra normalmente.

A persistência é local, em um tasks.json na raiz do projeto, resolvido relativamente à raiz do repositório (independe do diretório de trabalho). A ausência do arquivo equivale a uma lista vazia; a primeira gravação o cria. Um tasks.json com JSON inválido faz qualquer comando falhar com mensagem clara e sem sobrescrever o arquivo.

Numa segunda fase o repositório ganhou uma camada MCP. Ela não é um produto novo: reusa a mesma camada de domínio e o mesmo armazenamento, sem duplicar regras. O núcleo (src/) não mudou para a extensão existir e continua sem dependências externas.


Related MCP server: questlog-mcp

Architecture

src/ — núcleo, sem dependências externas

Arquivo

Responsabilidade

src/task-service.js

Regras de negócio. Domínio puro sobre state = { tasks, nextId }: cria, lista e conclui tarefas devolvendo sempre um novo estado imutável. Não toca filesystem, console nem process.

src/store.js

Armazenamento. Única parte que conhece o formato do arquivo e toca o filesystem. ENOENT = estado vazio (sem criar arquivo); JSON inválido = erro identificável (CORRUPT_STORAGE).

src/cli.js, src/index.js

Interface CLI. Parsing de argv, validação de formato, formatação da saída e exit codes. index.js é o shim de processo; cli.js orquestra store + domínio.

mcp/ — camada adaptadora

Arquivo

Responsabilidade

mcp/server.js

Servidor MCP. Fiação de protocolo: registra os handlers tools/list e tools/call, despacha por nome através de um Map de tools. Nenhuma regra de negócio.

mcp/tools/*.js

Tools. Adaptadores finos: validam o formato do argumento na borda (quando há argumento) e delegam a src/task-service.js + src/store.js.

mcp/storage-path.js

Resolve o mesmo tasks.json da raiz que a CLI usa — de propósito: uma tarefa criada por um caminho aparece no outro.

mcp/project-context.js

Helper somente leitura usado por project_status: coleta nome do projeto (de package.json) e contexto Git (branch, último commit). Qualquer falha externa vira null.

O MCP funciona como camada adaptadora sobre o domínio. A lógica de negócio permanece independente da interface: as tools consomem src/, e src/ não conhece mcp/. As dependências do SDK de MCP ficam confinadas a mcp/ e ao package.json da raiz.

Detalhes, diagramas e trade-offs: docs/architecture.md.


MCP Integration

  • Servidor: mcp/server.js (inicie com npm run mcp ou node mcp/server.js)

  • Transporte: JSON-RPC 2.0 sobre stdio. stdout é reservado ao protocolo; o diagnóstico de conexão (task-cli MCP server conectado (stdio)) vai para stderr.

Tools disponíveis

Tool

O que faz

Escreve em tasks.json?

add_task

Cria uma tarefa pendente e retorna o id atribuído.

Sim

list_tasks

Lista as tarefas (id, status, descrição).

Não

complete_task

Conclui uma tarefa pendente e persiste a alteração.

Sim

project_status

Retorna contexto somente leitura do projeto: nome, branch atual, último commit e contagem de tarefas (total, done, pending).

Não

project_status não altera estado e não modifica tasks.json: nunca chama a persistência, reutiliza a camada de domínio apenas para ler as tarefas, e consulta Git/package.json só para leitura. Falha ao consultar o Git (fora de um repositório, git ausente no PATH) resulta em null no campo correspondente, sem derrubar o servidor.

Formato do retorno de project_status:

{
  "projectName": "claude-lab",
  "git": { "branch": "main", "lastCommit": "<hash> <assunto>" },
  "tasks": { "total": 3, "done": 1, "pending": 2 }
}

Example Usage

CLI

$ node src/index.js add "Estudar MCP"
Tarefa 1 adicionada

$ node src/index.js list
[ ] 1  Estudar MCP

$ node src/index.js done 1
Tarefa 1 concluída

MCP / Claude Code

O cliente de IA faz a negociação MCP e então chama as tools. Um diálogo típico:

Usuário: "Crie uma tarefa chamada estudar MCP" Claude: chama add_task com { "description": "estudar MCP" } Resultado: Tarefa 1 adicionada (structuredContent: { id: 1, description: "estudar MCP", done: false })

Usuário: "Liste minhas tarefas" Claude: chama list_tasks com {} Resultado: [ ] 1 estudar MCP

Usuário: "Qual o status do projeto?" Claude: chama project_status com {} Resultado: nome, branch, último commit e contagem de tarefas — sem tocar em tasks.json

Como CLI e MCP usam o mesmo tasks.json, uma tarefa criada por add_task aparece em node src/index.js list, e vice-versa.


Technical Highlights

  • Separação entre domínio e interfaces. As regras vivem só em src/task-service.js; CLI e MCP são bordas finas por cima.

  • MCP como adaptador, não produto novo. Nenhuma regra reimplementada em mcp/; o núcleo permanece sem dependências externas.

  • JSON-RPC 2.0 sobre stdio. stdout exclusivo do protocolo; logs em stderr — condição para o cliente MCP não quebrar o parsing.

  • Integração com Claude Code. As quatro tools são chamáveis diretamente por um cliente de IA, incluindo project_status para contexto de repositório.

  • Testes unitários para o domínio, a CLI e as tools MCP.

  • Teste de integração que sobe o servidor MCP real como subprocesso e exercita o handshake initializetools/listtools/call por JSON-RPC.

  • Isolamento de armazenamento nos testes. O caminho do tasks.json é injetável; as suítes usam armazenamento temporário e nenhum teste escreve no tasks.json real.

  • Sem duplicação de regra de negócio. Um único ponto de verdade para validação, contagem e semântica de persistência.


Testing

node --test

Test runner nativo do Node.js (node:test), sem framework. Atualmente 9 suítes em test/, 84 testes, todos passando:

  • Domíniotest/task-service.test.js (regras puras, imutabilidade, nextId).

  • CLItest/add.test.js, test/list.test.js, test/done.test.js, test/cli.test.js (comportamento ponta a ponta, ajuda de uso, exit codes).

  • Tools MCPtest/mcp-tools.test.js (as três tools de tarefa), test/project-status.test.js (formato do contrato, estado vazio, contagem, arquivo corrompido, somente leitura).

  • Integração MCPtest/mcp-server-integration.test.js (servidor real sobre stdio; valida que stdout só carrega JSON-RPC e que o tasks.json real não muda).

  • Hook do laboratóriotest/block-agent-commit.test.js.

Algumas suítes sobem subprocessos (CLI, hook, servidor MCP) para validar comportamento de entrada real; as demais chamam as funções diretamente.


Design Decisions

  • Por que o MCP não fica dentro de src/. O núcleo é stdlib-only por contrato — testável, sem árvore de dependências, fácil de auditar. O SDK de MCP (@modelcontextprotocol/sdk) é uma dependência real e fica isolada em mcp/. As tools consomem src/; a dependência nunca vaza para o domínio.

  • Por que project_status é somente leitura. Não existe operação de escrita de "status" no produto. Torná-la só-leitura evita expandir o escopo, mantém a tool segura de chamar a qualquer momento e permite degradação graciosa quando o Git não está disponível (campos null em vez de erro).

  • Por que manter tasks.json nesta versão. O apetite do projeto é pequeno: um arquivo JSON local resolve a persistência sem servidor, sem schema, sem migração. Trocar por um banco só se justifica se o escopo crescer.

  • Trade-offs aceitos. Sem controle de concorrência — CLI e servidor MCP gravando ao mesmo tempo podem se sobrescrever (documentado como No-go). project_status invoca git de forma síncrona (execFileSync, sem shell, com timeout), o que bloqueia o event loop do servidor por alguns segundos no pior caso e não tem cache.


Future Improvements

Fora do escopo atual, mas compatíveis com a arquitetura:

  • Persistência em banco (SQLite/Postgres) atrás da mesma interface de store.js, sem tocar no domínio.

  • Controle de concorrência — lock cooperativo de arquivo entre CLI e servidor MCP.

  • Autenticação para MCP remoto, caso o transporte deixe de ser stdio local.

  • Cache de informações Git em project_status (TTL curto) para eliminar o custo de subprocesso por chamada; alternativamente, git assíncrono.

  • Observabilidade — logs estruturados em stderr e métricas básicas de uso das tools.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers