~ / tutoriais /hooks-slash-commands-e-mcps-a-anatomia-de-um-harness-produtivo $ _

Hooks, Slash Commands e MCPs: a anatomia de um harness produtivo

Lucas Souza Lucas Souza 10 min de leitura Tutoriais
Hooks, Slash Commands e MCPs: a anatomia de um harness produtivo

A maioria dos devs que começam a usar Claude Code trata a ferramenta como autocomplete glorificado. Abre o terminal, digita prompt, espera código.

Funciona. Mal.

O que muda o jogo é entender que o Claude Code não é um modelo solto: é um harness em volta de um modelo. E esse harness tem três pontos de extensão que você controla — hooks, slash commands (hoje skills) e MCP servers. Saber qual usar em cada situação é a diferença entre prompt bonito e ferramenta que aguenta produção.

Neste post, vou destrinchar peça por peça e mostrar três exemplos do harness que eu uso todo dia em projetos Laravel.

TL;DR

  • O que é: Anatomia das três extensões do Claude Code (hooks, slash commands, MCPs) e quando usar cada uma.
  • Stack/Modelos: Claude Code (CLI), Laravel 11+, PHP 8.3, MCP server custom em Laravel.
  • Custo/Acesso: Claude Code requer assinatura Anthropic. Hooks, skills e MCPs são gratuitos e configurados no seu projeto.
  • Repositório/Link útil: Claude Code docs.

O que é "harness" e por que isso importa

Modelo é o cérebro. Harness é o corpo.

Quando você roda claude no terminal, o que está rodando é um agent loop com gerenciamento de contexto, sistema de permissões, leitura de arquivos, execução de bash, sub-agentes e várias outras peças. O modelo Claude é só o orquestrador. Tudo que está em volta — o que ele lê antes de responder, o que ele tem permissão de executar, o que ele pode chamar como ferramenta — é o harness.

E o ponto é: você pode mexer nesse harness.

A Anthropic expõe três pontos de extensão claros:

  1. Hooks — disparam em eventos do ciclo de vida (antes de uma tool, depois de uma edição, ao iniciar sessão). Determinísticos, executados sempre.
  2. Slash commands — prompts pré-fabricados que você invoca com /nome (hoje implementados como skills, com o caminho legado .claude/commands/ ainda funcionando).
  3. MCP servers — ferramentas externas que o modelo pode chamar como qualquer outra tool nativa (Read, Bash, Edit).

Os três resolvem problemas diferentes. Misturar os papéis é o erro mais comum. Vamos por partes.

Hooks: o que entra e sai da execução

Hook é gatilho determinístico. Você não pede para o modelo lembrar de rodar pint depois de editar um arquivo PHP. Você configura um hook PostToolUse que roda pint toda vez que o Edit ou Write toca um .php. Sem prompt, sem esquecimento.

A documentação oficial lista mais de 30 eventos. Os que você vai usar 90% do tempo:

  • PreToolUse — antes de uma tool rodar. Pode bloquear, modificar input ou pedir aprovação.
  • PostToolUse — depois que rodou. Bom para validação, formatação, logging.
  • SessionStart — ao abrir a sessão. Bom para injetar contexto fresco (branch atual, status do banco, último deploy).
  • UserPromptSubmit — quando você submete prompt. Pode bloquear ou enriquecer.
  • Stop — quando o Claude termina o turno. Bom para checks finais.

Configuração vai em .claude/settings.json (compartilhado via git) ou .claude/settings.local.json (gitignored). O formato:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/laravel-pint.sh"
          }
        ]
      }
    ]
  }
}

E o script:

#!/bin/bash
# .claude/hooks/laravel-pint.sh
FILE_PATH=$(jq -r '.tool_input.file_path')

if [[ "$FILE_PATH" == *.php ]]; then
  ./vendor/bin/pint "$FILE_PATH" --quiet
fi

exit 0

Pronto. Toda vez que o Claude editar um arquivo PHP no projeto, o pint roda. Sem precisar lembrar, sem precisar pedir, sem virar comentário no CLAUDE.md.

A regra que eu aplico para decidir se um comportamento merece virar hook é simples: se a falha humana de esquecer custa mais do que o overhead de configurar, vira hook. Lint depois de editar? Hook. Validar que migration nova tem down()? Hook. Bloquear git push --force em main? Hook (com exit code 2 no PreToolUse do Bash).

Hooks suportam cinco tipos de handler — comando shell, HTTP endpoint, MCP tool, prompt LLM e subagent. Mas 95% das vezes você quer só command. Ele é o mais previsível e o que mais bate com a tese de "determinístico, sem ambiguidade".

Slash commands: comprimir prompt repetido em uma palavra

Se você se pega digitando o mesmo bloco de instruções três vezes na semana, isso devia virar slash command.

Slash command no Claude Code (oficialmente skill, na nomenclatura nova) é um arquivo Markdown em .claude/commands/<nome>.md ou .claude/skills/<nome>/SKILL.md. Quando você digita /<nome>, o conteúdo do arquivo é injetado como prompt. O modelo executa.

Exemplo do meu harness, um /pr-resumo que eu uso pra fechar pull request em projeto Laravel:

---
description: Resume um PR pronto para revisão. Use quando terminei um feature e quero descrição clara para subir.
allowed-tools: Bash(git *) Bash(gh *)
---

## Contexto do branch

- Diff atual: !`git diff main...HEAD`
- Arquivos alterados: !`git diff main...HEAD --name-only`
- Commits do branch: !`git log main..HEAD --oneline`

## Sua tarefa

Escreva uma descrição de PR em três partes:

1. **O que muda**: uma frase, foco no comportamento de produto.
2. **Por que**: bug que estava aberto, dor reportada, ou trade-off arquitetural.
3. **Como testar**: checklist de 3 a 5 itens que um revisor consegue executar.

Tom: técnico, direto, sem floreio. Não use bullets de marketing tipo "implementa funcionalidade X para melhorar Y".

A linha !`git diff main...HEAD` é açúcar do Claude Code: o shell roda antes do modelo ver o conteúdo, e o output substitui o placeholder. Então o modelo não precisa pensar em chamar git diff, chega tudo pronto no prompt.

A diferença entre hook e slash command é o gatilho. Hook dispara automático em evento do ciclo de vida. Slash command dispara quando você digita /. Se a ação é "sempre que X acontecer", é hook. Se é "quando eu pedir", é slash command.

Outro ponto que muda o jogo: skills suportam disable-model-invocation: true. Isso impede o Claude de invocar a skill por conta própria, só você dispara. Para coisas com efeito colateral (deploy, commit, mandar mensagem), use sempre. Você não quer o modelo decidindo deployar porque "o código parecia pronto".

MCPs: dar acesso direto a ferramentas reais

Hooks e slash commands operam dentro do que o Claude Code já oferece. MCP é o ponto onde você estende o conjunto de ferramentas que o modelo pode chamar.

MCP (Model Context Protocol) é um padrão aberto, criado pela Anthropic, que define um contrato entre cliente (Claude Code, Cursor, ChatGPT) e servidor (qualquer ferramenta que você queira expor). A analogia oficial é "USB-C para aplicações de IA": implementa uma vez, conecta em qualquer cliente que suporte o protocolo.

Para o dev Laravel, isso muda tudo. Quer que o Claude consulte o seu banco Postgres em desenvolvimento? Sobe um MCP server. Quer que ele leia tickets do Linear? MCP. Quer expor a API interna do seu produto pra ele consumir como ferramenta? MCP.

Adicionar um MCP server no Claude Code é um comando:

# Servidor remoto via HTTP (recomendado)
claude mcp add --transport http notion https://mcp.notion.com/mcp

# Servidor local via stdio
claude mcp add --transport stdio --env DB_URL=postgres://... \
  meu-banco -- node ./mcp-servers/db.js

Os escopos importam:

  • --scope local (default): só você, só esse projeto.
  • --scope project: compartilhado via .mcp.json no repo, time inteiro herda.
  • --scope user: global, todos os seus projetos.

Para o blog Beer & Code, eu mantenho um MCP server custom em Laravel que expõe ferramentas para criar, agendar e publicar posts. O Claude chama create-post, upload-image, publish-post como se fossem tools nativas. Não preciso copiar HTML, não preciso entrar no admin, não preciso explicar o schema toda vez. O contrato está no servidor, o modelo descobre pelas tool descriptions.

A regra de decisão para MCP é a mais clara das três: se você se pega copiando dados de outro lugar para colar no Claude Code, isso devia ser MCP. Logs do Sentry, queries no banco, status do Jira, métricas do Datadog. Cada copy-paste é um sinal de que falta um MCP.

Quando usar qual

A confusão mais comum é tentar resolver com slash command algo que devia ser hook, ou querer que o MCP "lembre" de rodar lint. Os papéis são distintos:

Quero... Use
Garantir que algo aconteça sempre, sem o modelo decidir Hook
Comprimir um prompt longo que repito muito Slash command
Dar acesso a uma ferramenta ou fonte de dado externa MCP
Bloquear comportamento perigoso (rm, push force, drop table) Hook (PreToolUse com exit code 2)
Injetar contexto no início da sessão (branch, banco, ambiente) Hook (SessionStart)
Fluxo manual repetível (resumir PR, gerar migration, fechar issue) Slash command
Consultar banco, ticket, dashboard, API interna MCP

Combinar os três é onde o harness fica produtivo. No meu setup do blog: um hook injeta status do post atual no SessionStart, um slash command /escrever-post orquestra a pesquisa e escrita, e o MCP server publica de fato. Cada peça faz uma coisa só, e cada uma é trocável.

Limitações e pontos de atenção

Antes de sair configurando, três coisas que você vai aprender na dor se não souber agora.

Hook que falha em loop trava sessão. Hook com exit code 2 bloqueia a tool. Se o seu hook tem bug e sempre retorna 2, o Claude não consegue mais editar nada e você fica preso. Sempre logue para arquivo e teste o script à parte antes de plugar.

Slash command pesado custa contexto. Skills entram no contexto da conversa e ficam lá pelo resto da sessão. Se a sua skill tem 5000 tokens de instrução, está pagando isso em todo turno seguinte. A doc da Anthropic recomenda manter SKILL.md abaixo de 500 linhas e jogar referência detalhada em arquivos auxiliares.

MCP server é superfície de ataque. A Anthropic alerta explicitamente para o risco de MCP de terceiros: "be especially careful when using MCP servers that could fetch untrusted content" (fonte). MCP que faz fetch de conteúdo arbitrário pode injetar prompt no seu modelo. Para servers internos do seu produto, isso é controlado. Para servers públicos, leia o código antes.

E uma quarta, que vale para os três: versione no git. Hooks em .claude/settings.json, slash commands em .claude/commands/ ou .claude/skills/, MCP em .mcp.json. Tudo isso é configuração de projeto e devia ser revisado em PR como qualquer outro código.

FAQ rápido

Slash command e skill são a mesma coisa? Hoje, sim. A Anthropic mesclou os dois: .claude/commands/deploy.md e .claude/skills/deploy/SKILL.md ambos criam o /deploy. A diferença é que skill suporta diretório com arquivos auxiliares, frontmatter rico e invocação automática pelo modelo. Os arquivos antigos em .claude/commands/ continuam funcionando.

Posso usar hook para chamar um MCP tool? Sim. O tipo de hook mcp_tool faz exatamente isso. É útil quando a validação que você quer rodar já existe como ferramenta no seu MCP server e você não quer reimplementar em bash.

MCP funciona offline? Depende do transport. stdio é processo local, roda offline. http e sse precisam de rede. Para dev Laravel local, prefira stdio quando puder. Tem menos latência e não depende de uptime de servidor remoto.

Como debugar quando um hook não dispara? Digite /hooks no Claude Code. O menu mostra todos os eventos configurados, matchers e fonte da configuração (user, project, plugin). Se o seu hook não aparece ali, o problema é de configuração, não de execução.

Conclusão

Tratar Claude Code como prompt bonito é deixar 70% do valor na mesa. O harness está ali, exposto, e cada peça resolve um problema diferente: hook automatiza o que não pode falhar, slash command comprime o que você repete, MCP estende o que o modelo pode tocar.

A pergunta certa não é "qual prompt eu mando?". É "que peça do meu fluxo de dev devia estar versionada no .claude/ em vez de viver na minha cabeça?".

Quanto mais do seu processo entra no harness, menos ambiguidade sobra para o modelo. E mais o Claude vira o que ele devia ser desde o começo: ferramenta de engenharia, não brinquedo de chat.

Lucas Souza
Lucas Souza

{AI Engineer} — apaixonado por Laravel, arquitetura de software e construir produtos com impacto. Compartilho aqui tutoriais, descobertas e reflexões sobre o dia a dia de engenharia.

VirguIA

beer & code assistant

conectando…

Não foi possível iniciar o chat agora.

tocando