feat(infraestrutura): mcp-dev v3.0 — SDK TypeScript v2 e transporte HTTP obrigatórios

Regra de Ouro adicionada ao topo da skill: SDK v2 (@modelcontextprotocol/server),
API de alto nível McpServer+registerTool (API de baixo nível Server+setRequestHandler
proibida), transporte HTTP obrigatório (stdio só como excepção documentada), e
proibição explícita de interpolação de dados em comandos shell/SSH.

Motivado por revisão de segurança ao mcp-kivicare (19-08-2026): 8 bugs reais
corrigidos em produção, incluindo injecção de comandos shell crítica e hooks
nativos disparados com o tipo errado. Regras adicionadas para nunca mais acontecer
em silêncio.

- SKILL.md: nova secção Regra de Ouro, API/annotations/capabilities actualizadas
  para v2, transportes HTTP obrigatório, changelog v3.0.0, healing log com os
  3 bugs mais graves do incidente.
- references/templates.md: reescrito para createMcpHandler + createMcpExpressApp
  (HTTP) e serveStdio (excepção), package.json com pacotes v2.
- references/best-practices.md: capabilities/annotations/error handling
  actualizados para API v2, checklist de segurança com regra anti-injecção de
  comandos shell, tabela de transportes com HTTP obrigatório.
- references/evaluation-guide.md: script de evaluations reescrito para
  Client+StreamableHTTPClientTransport in-process (sem porta/socket).
- infraestrutura: 1.2.2 -> 1.3.0
This commit is contained in:
2026-08-19 09:16:31 +01:00
parent 4e33c1d697
commit 18e48db490
5 changed files with 240 additions and 321 deletions
+61 -38
View File
@@ -1,12 +1,24 @@
---
name: mcp-dev
description: Desenvolvimento e teste de servidores MCP — criação, configuração e gestão de MCPs customizados seguindo os padrões Descomplicar com transporte StreamableHTTP.
description: 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 novo, e toda a edição estrutural de um MCP existente, tem de cumprir isto:
1. **SDK oficial v2** ([ts.sdk.modelcontextprotocol.io/v2](https://ts.sdk.modelcontextprotocol.io/v2/)) — pacotes `@modelcontextprotocol/server` (+ `@modelcontextprotocol/client` só 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ível `McpServer` + `registerTool`/`registerResource`/`registerPrompt` — a API de baixo nível `Server` + `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).
2. **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 sempre `serveStdio` do SDK v2 (nunca `StdioServerTransport` manual).
3. **Node.js 20+, ESM (`"type": "module"`), `zod/v4` (peer `^4.2.0`)** — nunca `zod` v3, nunca `require()`.
4. **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 ... "..."`. Ver `references/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.
## Contexto NotebookLM
Consultar ANTES de executar:
@@ -48,7 +60,7 @@ mcp__notebooklm__notebook_query({
1. Consultar NotebookLM (notebook acima)
2. Verificar MCPs existentes — evitar duplicação
3. Identificar ferramentas necessárias e nomear segundo padrão
4. Definir transporte: HTTP (recomendado) ou stdio
4. Transporte: **HTTP obrigatório** (Streamable HTTP via SDK v2, `createMcpHandler`). stdio só com justificação escrita explícita — ver secção Transportes.
5. Criar spike/PoC se tecnologia desconhecida
**Nomenclatura de tools:** `{serviço}_{acção}_{recurso}` em snake_case, máx. 40 caracteres.
@@ -64,30 +76,27 @@ getCustomerNotes (camelCase), get_customer_notes_with_all_billing_details (>40 c
### Fase 2: Implementation
1. Scaffold local no desktop: `/media/ealmeida/Dados/Dev/<nome-mcp>/`
2. Implementar tools com **annotations obrigatórias** e validação Zod
3. Definir capabilities completas (tools + resources + prompts)
2. Implementar tools com **annotations obrigatórias** e validação Zod (`z.object(...)` de `zod/v4`)
3. `McpServer` deriva capabilities automaticamente de `registerTool`/`registerResource`/`registerPrompt` — nunca declarar capabilities à mão (isso é API de baixo nível v1, proibida)
4. Responses em **dual format**: Markdown (text) + JSON (structuredContent)
5. Erros **accionáveis**: causa + próximos passos
**Annotations obrigatórias em cada tool:**
**Annotations obrigatórias em cada tool** — vão directo na config de `registerTool` (sem passo de inferência separado no v2):
```typescript
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)
}
```
Usar `inferAnnotations()` do SDK para inferência automática:
```typescript
import { inferAnnotations } from '@modelcontextprotocol/sdk/server/utils.js';
const annotated = inferAnnotations(tool);
```
**Capabilities — sempre completas (anti-erro 471):**
```typescript
capabilities: { tools: {}, resources: {}, prompts: {} }
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:**
@@ -119,13 +128,15 @@ return {
2. `pnpm audit` — sem vulnerabilidades críticas
3. Validar limites de nomes de tools (máx. 40 chars)
4. Testar build: `npm run build`
5. Testar manualmente: `echo '{"jsonrpc":"2.0","method":"tools/list","id":1}' | node dist/index.js`
5. 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"}'`
6. Documentar decisões em ADR se necessário
**Grep de validação rápida:**
```bash
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
@@ -162,12 +173,11 @@ npm run eval:ci # build + evaluations (para CI/CD)
├── package.json
├── tsconfig.json
├── src/
│ ├── index.ts # Entry point stdio
│ ├── index-http.ts # Entry point HTTP
│ └── tools/
│ └── index.ts # Definição de tools
│ ├── 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
│ └── run-evals.ts # Evaluations automáticas (Client + StreamableHTTPClientTransport in-process)
├── .env.example
├── README.md
└── .gitignore
@@ -179,7 +189,7 @@ npm run eval:ci # build + evaluations (para CI/CD)
## Configuração
**~/.claude.json (HTTP — recomendado):**
**~/.claude.json (HTTP — obrigatório):**
```json
{
"mcpServers": {
@@ -188,11 +198,11 @@ npm run eval:ci # build + evaluations (para CI/CD)
}
```
**~/.claude.json (stdio — local):**
**~/.claude.json (stdio — apenas excepção documentada):**
```json
{
"mcpServers": {
"<nome>": { "command": "node", "args": ["/home/ealmeida/mcp-servers/<nome>/dist/index.js"] }
"<nome>": { "command": "node", "args": ["/home/ealmeida/mcp-servers/<nome>/dist/stdio.js"] }
}
}
```
@@ -203,9 +213,9 @@ npm run eval:ci # build + evaluations (para CI/CD)
| Transporte | Estado | Quando usar |
|------------|--------|-------------|
| StreamableHTTP | **Recomendado** | Novos MCPs, acesso remoto, gateway |
| stdio | Válido | Claude Code local, scripts únicos |
| SSE | Deprecated | Retrocompatibilidade apenas |
| 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 |
---
@@ -224,9 +234,9 @@ npm run eval:ci # build + evaluations (para CI/CD)
| Ficheiro | Conteúdo |
|----------|----------|
| `references/best-practices.md` | Nomenclatura, annotations, segurança, SQL Perfex |
| `references/evaluation-guide.md` | Guia completo de evaluations, script automatizado |
| `references/templates.md` | Templates TypeScript prontos (stdio, HTTP, systemd) |
| `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.
@@ -234,6 +244,16 @@ npm run eval:ci # build + evaluations (para CI/CD)
## Changelog
### v3.0.0 (2026-08-19)
- **SDK TypeScript v2 obrigatório** (`@modelcontextprotocol/server`, ver [ts.sdk.modelcontextprotocol.io/v2](https://ts.sdk.modelcontextprotocol.io/v2/)) — `@modelcontextprotocol/sdk` v1 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 de `registerTool`.
- 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 em `mcp-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)
@@ -273,7 +293,7 @@ npm run eval:ci # build + evaluations (para CI/CD)
---
*Skill v2.0.0 | Descomplicar® | 2026-03-10*
*Skill v3.0.0 | Descomplicar® | 2026-08-19*
---
@@ -283,6 +303,9 @@ Registo de erros conhecidos e como evitá-los. Lido automaticamente antes de exe
```jsonl
{"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.*