17 KiB
name, description
| name | description |
|---|---|
| mcp-dev | Desenvolvimento e teste de servidores MCP — criação, configuração e gestão de MCPs customizados. SDK TypeScript v2 (@modelcontextprotocol/server) e transporte HTTP são obrigatórios, sem excepção silenciosa. |
/mcp-dev - Desenvolvimento de MCPs
Skill para criação, configuração e gestão de servidores MCP customizados.
Regra de Ouro — obrigatória, sem excepção silenciosa
Todo MCP próprio da Descomplicar — novo, existente, ou sob edição estrutural — tem de cumprir isto. A regra aplica-se ao código que nós escrevemos; ver §Política para MCPs de terceiros mais abaixo. Um MCP que falhe qualquer ponto abaixo está fora de conformidade e tem de ser migrado (não "deixado como está" por funcionar).
- SDK oficial v2 (ts.sdk.modelcontextprotocol.io/v2) — pacotes
@modelcontextprotocol/server(+@modelcontextprotocol/clientsó em testes/evaluations).@modelcontextprotocol/sdk(v1) está descontinuado nesta casa — nunca instalar, nunca copiar código de um MCP antigo que o use sem migrar primeiro. API de alto nívelMcpServer+registerTool/registerResource/registerPrompt— a API de baixo nívelServer+setRequestHandler(Schema, …)está proibida (reintroduz classes de bugs que o v2 elimina por desenho, incluindo o anti-padrão de capabilities manuais que causava o erro 471). - Transporte HTTP obrigatório —
createMcpHandler(Streamable HTTP), tipicamente montado com@modelcontextprotocol/express+@modelcontextprotocol/node.stdio(serveStdio) só é aceitável quando o host tem de lançar o processo directamente e não existe gateway — nesse caso documentar a excepção no README do MCP com a razão concreta, e usar sempreserveStdiodo SDK v2 (nuncaStdioServerTransportmanual). - Node.js 20+, ESM (
"type": "module"),zod/v4(peer^4.2.0) — nuncazodv3, nuncarequire(). - Nunca construir comandos shell/SQL por interpolação de string quando o MCP faz ponte para SSH/CLI externos (ex.: WP-CLI). Dados do utilizador passam sempre por STDIN (texto ou base64), nunca concatenados num comando
ssh ... "...". Verreferences/best-practices.md§Segurança — regra escrita depois de uma injecção de comandos real ter sido encontrada e corrigida em produção (mcp-kivicare, 19-08-2026, ver Healing Log no fim deste ficheiro).
Se qualquer um destes 4 pontos não puder ser cumprido, parar e perguntar antes de prosseguir — não decidir sozinho por uma alternativa mais rápida.
⚠️ Armadilha — pacote v2 ≠ API v2. Importar @modelcontextprotocol/server não garante conformidade: um MCP só está conforme se usar a API de alto nível McpServer + registerTool. Se o código (mesmo importando o pacote v2) chamar setRequestHandler ou instanciar Server, está a usar a API de baixo nível proibida — é não-conforme. Verificação: grep -oE 'setRequestHandler|McpServer\(|registerTool' dist/*.js | sort | uniq -c — deve mostrar McpServer + registerTool e zero setRequestHandler. Caso real: mcp-desk-project-minimal (26-08-2026) importava só o pacote v2 mas usava setRequestHandler — não-conforme apesar do pacote certo.
Política para MCPs de terceiros e migração de existentes
MCPs de terceiros (pacotes npm/uvx que apenas instalamos e corremos — ex.: deepl, lighthouse, notebooklm, puppeteer, echarts, pixabay, authentik, spaceship, replicate, context7, gitea-mcp): a Regra de Ouro não lhes aplica — não controlamos o código deles e não os podemos migrar (só dar fork ou substituir). Correm como estão, em v1. Só substituir se uma alternativa v2/nativa (ex.: Python FastMCP) fizer sentido para os mais usados — decisão por caso, não política global.
MCPs próprios já existentes em v1 (ex.: desk-crm-v3, moloni, mem0, wikijs, cwp, reonic, imap-enterprise, youtube-research, carl-mcp, mcp-wayland, gestix-mcp stdio): estão fora de conformidade e devem ser migrados para v2. Antes de cada migração em produção: fazer snapshot do código e dist actual (git commit ou cópia) para rollback rápido se o build v2 partir algo. Migrar é mudança de API, não bump de versão — é trabalho por servidor, não automatizável em massa.
Contexto NotebookLM
Consultar ANTES de executar:
mcp__notebooklm__notebook_query({
notebook_id: "73102308-70ef-403e-9be9-eae0cfc62d55",
query: "<adaptar ao contexto do pedido>"
})
Procedimentos obrigatórios — consultar antes de criar/modificar MCPs:
- PROC-MCP-Desenvolvimento.md — Guia oficial v2.4
- PROC-MCP-Troubleshooting-Erro-471.md
- PROC-MCP-Google-Auth.md
Regra #48: Novos MCPs são desenvolvidos localmente no desktop em
/media/ealmeida/Dados/Dev/<nome-mcp>(CT 102 extinto em 20-04-2026). Após validação, mover para~/mcp-servers/em produção.
Comandos
| Comando | Descrição |
|---|---|
/mcp-dev create <nome> |
Criar scaffold TypeScript completo |
/mcp-dev config <nome> |
Configurar MCP em ~/.claude.json |
/mcp-dev test <nome> |
Testar conexão e listar ferramentas |
/mcp-dev eval <nome> |
Executar evaluations do MCP |
/mcp-dev docs <nome> |
Gerar documentação Obsidian |
/mcp-dev list |
Listar MCPs em ~/mcp-servers/ |
/mcp-dev status |
Estado dos MCPs activos na sessão |
Workflow de 4 Fases
Fase 1: Research
- Consultar NotebookLM (notebook acima)
- Verificar MCPs existentes — evitar duplicação
- Identificar ferramentas necessárias e nomear segundo padrão
- Transporte: HTTP obrigatório (Streamable HTTP via SDK v2,
createMcpHandler). stdio só com justificação escrita explícita — ver secção Transportes. - Criar spike/PoC se tecnologia desconhecida
Nomenclatura de tools: {serviço}_{acção}_{recurso} em snake_case, máx. 40 caracteres.
# Correcto
get_customer_notes, create_project_task, list_invoice_items
# Errado
getCustomerNotes (camelCase), get_customer_notes_with_all_billing_details (>40 chars)
Fase 2: Implementation
- Scaffold local no desktop:
/media/ealmeida/Dados/Dev/<nome-mcp>/ - Implementar tools com annotations obrigatórias e validação Zod (
z.object(...)dezod/v4) McpServerderiva capabilities automaticamente deregisterTool/registerResource/registerPrompt— nunca declarar capabilities à mão (isso é API de baixo nível v1, proibida)- Responses em dual format: Markdown (text) + JSON (structuredContent)
- Erros accionáveis: causa + próximos passos
Annotations obrigatórias em cada tool — vão directo na config de registerTool (sem passo de inferência separado no v2):
server.registerTool(
'get_customer',
{
description: 'Obtém dados de um cliente pelo ID',
inputSchema: z.object({ customer_id: z.number().int().positive() }),
annotations: {
readOnlyHint: true, // não modifica estado
destructiveHint: false, // não é destrutiva
idempotentHint: true, // mesmo resultado em chamadas repetidas
openWorldHint: false, // opera em sistema fechado (interno)
},
},
async ({ customer_id }) => { /* ... */ }
);
Dual format de resposta:
return {
content: [{ type: 'text', text: `# Cliente ${d.name}\n**Email:** ${d.email}` }],
structuredContent: { id: d.id, name: d.name, email: d.email }
};
Erros accionáveis:
return {
content: [{
type: 'text',
text: [
`Erro ao obter cliente ID ${id}.`,
`Causa: ${error.message}`,
`Verificar: 1) ID existe 2) Permissões 3) Conexão BD`
].join('\n')
}],
isError: true
};
Fase 3: Review
- Checklist de segurança (ver
references/best-practices.md) pnpm audit— sem vulnerabilidades críticas- Validar limites de nomes de tools (máx. 40 chars)
- Testar build:
npm run build - Testar manualmente via HTTP:
curl -s -X POST http://127.0.0.1:32XX/mcp -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' - Documentar decisões em ADR se necessário
Grep de validação rápida:
grep -rn '`.*\${.*}`' src/ | grep -i 'select\|insert\|update\|delete'
grep -rn 'Math.random' src/
# Comandos shell/ssh interpolados (ver Regra de Ouro §4) — qualquer resultado exige revisão manual
grep -rn 'exec(\|execSync(\|spawn(.*sh\b' src/ | grep -i '\${'
Fase 4: Evaluations
Criar e executar testes estruturados antes do deploy.
10 evaluations obrigatórias por MCP (ver references/evaluation-guide.md):
| # | Pergunta | Cenário |
|---|---|---|
| 1 | Tool principal funciona com input válido? | Input mínimo válido |
| 2 | Input inválido retorna erro accionável? | Parâmetro obrigatório em falta |
| 3 | Recurso inexistente retorna erro claro? | ID=99999 |
| 4 | Operações idempotentes são estáveis? | Chamada repetida com mesmos params |
| 5 | Operações destrutivas apagam correctamente? | Delete em recurso existente |
| 6 | Tools read-only não alteram estado? | Get + verificar estado antes/depois |
| 7 | Paginação funciona com listas grandes? | 1000+ registos, página 2 |
| 8 | Auth inválida é rejeitada graciosamente? | API key removida |
| 9 | structuredContent é consistente com text? | Comparar campos nos dois formatos |
| 10 | Performance < 5s em p95? | 10 chamadas consecutivas |
Executar:
npm run eval # evaluations
npm run eval:ci # build + evaluations (para CI/CD)
Estrutura de Projecto
/media/ealmeida/Dados/Dev/mcp-<nome>/ # Desenvolvimento (local, desktop)
├── package.json
├── tsconfig.json
├── src/
│ ├── server.ts # Factory McpServer + registerTool (partilhado)
│ ├── http.ts # Entry point HTTP (createMcpHandler + Express) — obrigatório
│ └── stdio.ts # Entry point stdio (serveStdio) — só se excepção justificada
├── eval/
│ └── run-evals.ts # Evaluations automáticas (Client + StreamableHTTPClientTransport in-process)
├── .env.example
├── README.md
└── .gitignore
/home/ealmeida/mcp-servers/mcp-<nome>/ # Produção
Configuração
~/.claude.json (HTTP — obrigatório):
{
"mcpServers": {
"<nome>": { "type": "http", "url": "http://127.0.0.1:32XX/mcp" }
}
}
~/.claude.json (stdio — apenas excepção documentada):
{
"mcpServers": {
"<nome>": { "command": "node", "args": ["/home/ealmeida/mcp-servers/<nome>/dist/stdio.js"] }
}
}
Transportes
| Transporte | Estado | Quando usar |
|---|---|---|
| Streamable HTTP (SDK v2) | Obrigatório | Todos os MCPs novos — acesso remoto, gateway, múltiplos clientes |
stdio (SDK v2, serveStdio) |
Excepção documentada | Só quando o host lança o processo directamente e não há gateway. Justificar no README do MCP. |
| SSE | Deprecated | Retrocompatibilidade apenas — nunca usar em MCP novo |
MCPs Existentes (Referência)
| MCP | Path | Tools | Transporte |
|---|---|---|---|
| desk-crm-v3 | ~/mcp-servers/desk-crm-v3/ | 150+ | SSE :3100 |
| mem0 | gateway.descomplicar.pt/v1/mem0/mcp (remoto) | 5 | HTTP |
| google-workspace | (npm package) | 42 | stdio |
| filesystem | (npm package) | 8 | stdio |
Referências
| Ficheiro | Conteúdo |
|---|---|
references/best-practices.md |
Nomenclatura, annotations, segurança (incl. anti-injecção de comandos shell), SQL Perfex |
references/evaluation-guide.md |
Guia completo de evaluations, script automatizado (Client v2 in-process) |
references/templates.md |
Templates TypeScript v2 prontos (server factory, HTTP obrigatório, stdio excepcional, systemd) |
Agente especializado: mcp-protocol-developer — desenvolvimento complexo, debug, optimização.
v3.0.1 (2026-08-26)
- Esclarecida a armadilha "pacote v2 ≠ API v2" — importar
@modelcontextprotocol/servernão basta; um MCP só está conforme comMcpServer+registerTool, nuncasetRequestHandler/Server. Motivo:mcp-desk-project-minimalimportava o pacote v2 mas usava a API de baixo nível proibida. Incluído comando de verificação rápida. - Política de migração explícita — a Regra de Ouro aplica-se a todos os MCPs próprios (novos, existentes, edição estrutural); existentes em v1 devem ser migrados, com snapshot para rollback antes de tocar produção. MCPs de terceiros (npm/uvx) ficam fora do âmbito — não os controlamos, não os migramos.
v3.0.0 (2026-08-19)
- SDK TypeScript v2 obrigatório (
@modelcontextprotocol/server, ver ts.sdk.modelcontextprotocol.io/v2) —@modelcontextprotocol/sdkv1 descontinuado nesta casa. - Transporte HTTP obrigatório; stdio passa a excepção documentada (antes era "válido" sem restrição).
- API de baixo nível
Server+setRequestHandler(Schema, …)proibida — sóMcpServer+registerTool/registerResource/registerPrompt. - Capabilities manuais removidas do fluxo (v2 deriva automaticamente) — a secção "anti-erro 471" do v1 já não se aplica ao padrão obrigatório.
inferAnnotations()removido (não existe em v2) — annotations sempre inline na config deregisterTool.- Nova secção "Regra de Ouro" no topo do ficheiro, com 4 obrigações sem excepção silenciosa.
references/best-practices.md§Segurança recebeu regra de anti-injecção de comandos shell (bridge SSH/CLI), motivada por incidente real corrigido emmcp-kivicare(19-08-2026, 8 bugs, incl. injecção de comandos crítica).- Templates (
references/templates.md) reescritos para a API v2 (createMcpHandler,createMcpExpressApp,serveStdio).
v2.0.0 (2026-03-10)
- Refactorização: SKILL.md de 1163 para <500 linhas (progressive disclosure)
- Fase 4: Evaluations adicionada ao workflow (padrão mcp-builder Anthropic)
- Nomenclatura tools:
{serviço}_{acção}_{recurso}snake_case documentada - Annotations obrigatórias (readOnlyHint, destructiveHint, idempotentHint, openWorldHint)
inferAnnotations()mencionado como padrão automático- Dual format de resposta (JSON + Markdown) documentado
- Erros accionáveis com causa + próximos passos
- Extracto para
references/: best-practices, evaluation-guide, templates
v1.3.0 (2026-01-31)
- HTTP StreamableHTTP como padrão oficial
- SSE marcado como deprecated
- Checklist de segurança (audit 164 tools)
v1.2.0 (2026-01-27)
- Template SSE com http nativo
- systemd service template
v1.0.0 (2026-01-27)
- Versão inicial
Quando NÃO Usar
- Para tarefas fora do domínio de desenvolvimento MCP
- Quando outra skill mais específica está disponível
- Para operações simples que não requerem MCP
Protocolo
- Consultar NotebookLM e PROCs relevantes
- Executar as 4 fases: Research -> Implementation -> Review -> Evaluations
- Validar resultados antes de concluir
- Reportar status e próximos passos
Skill v3.0.1 | Descomplicar® | 2026-08-26
Healing Log
Registo de erros conhecidos e como evitá-los. Lido automaticamente antes de executar.
{"date":"","issue":"","fix":"","source":"user|auto"}
{"date":"2026-08-19","issue":"mcp-kivicare: runRawSqlQuery interpolava a query directamente num comando `ssh ... wp db query \"...\"` — injecção de comandos shell explorável","fix":"query sempre por STDIN (`wp db query < stdin`), nunca interpolada no comando remoto","source":"auto"}
{"date":"2026-08-19","issue":"mcp-kivicare: hooks nativos do KiviCare (kc_appointment_status_update) disparados com um inteiro em vez do objecto do modelo — TypeError fatal num listener nativo, registo órfão em produção","fix":"recarregar o modelo (ex.: KCAppointment::find($id)) antes de disparar qualquer hook nativo; ler a assinatura real do hook no código-fonte do sistema alvo antes de o chamar","source":"auto"}
{"date":"2026-08-19","issue":"mcp-kivicare: basic_data do paciente gravado como array PHP em vez do JSON que o sistema nativo espera (json_encode com JSON_UNESCAPED_UNICODE) — corrompia a leitura no admin nativo","fix":"replicar exactamente o formato de serialização do sistema nativo (grep ao código-fonte real), nunca assumir a partir da forma dos dados em memória","source":"auto"}
Adicionar nova linha após cada erro corrigido.