Files
claude-plugins/infraestrutura/skills/mcp-dev/references/evaluation-guide.md
T
ealmeida 18e48db490 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
2026-08-19 09:16:31 +01:00

8.8 KiB

MCP Evaluation Guide

Guia para criar e executar evaluations de servidores MCP. Baseado nos padrões do mcp-builder Anthropic.


O que são Evaluations MCP?

Evaluations são testes estruturados que validam o comportamento de um MCP em cenários reais. Não testam apenas se as ferramentas existem — testam se produzem resultados correctos, seguros e previsíveis.

Objectivos:

  • Garantir que cada tool faz o que a descrição promete
  • Detectar regressões após alterações
  • Validar edge cases e erros esperados
  • Confirmar que annotations correspondem ao comportamento real

Estrutura de uma Evaluation

Cada evaluation é definida em XML e contém:

<evaluation>
  <name>get_customer_basic</name>
  <description>Verifica que get_customer retorna dados completos de um cliente existente</description>
  <tool>get_customer</tool>
  <input>
    <customer_id>1</customer_id>
  </input>
  <expected>
    <contains>name</contains>
    <contains>email</contains>
    <not_error>true</not_error>
  </expected>
</evaluation>

10 Perguntas de Evaluation por MCP

Para cada MCP novo, responder a estas 10 perguntas criando um teste para cada uma:

<!-- 1. A tool mais básica funciona? -->
<evaluation id="1">
  <question>A tool {principal} retorna dados quando recebe input válido?</question>
  <scenario>Input mínimo válido</scenario>
  <assert>Resposta não é erro, contém campos esperados</assert>
</evaluation>

<!-- 2. Erros de input são tratados graciosamente? -->
<evaluation id="2">
  <question>O que acontece com input inválido (ID negativo, campo obrigatório em falta)?</question>
  <scenario>Input inválido ou em falta</scenario>
  <assert>Resposta tem isError=true, mensagem é accionável</assert>
</evaluation>

<!-- 3. O recurso inexistente é tratado correctamente? -->
<evaluation id="3">
  <question>O que retorna quando o recurso pedido não existe (ID=99999)?</question>
  <scenario>Recurso não encontrado</scenario>
  <assert>Mensagem clara "não encontrado", não retorna null silencioso</assert>
</evaluation>

<!-- 4. Operações de escrita são idempotentes quando declaradas? -->
<evaluation id="4">
  <question>Operações com idempotentHint=true produzem o mesmo resultado em chamadas repetidas?</question>
  <scenario>Chamar a mesma tool create/update duas vezes com os mesmos parâmetros</scenario>
  <assert>Segundo resultado idêntico ao primeiro ou erro controlado</assert>
</evaluation>

<!-- 5. Operações destrutivas têm confirmação ou são irreversíveis como declarado? -->
<evaluation id="5">
  <question>Tools com destructiveHint=true apagam realmente dados? O utilizador é avisado?</question>
  <scenario>Executar delete em recurso existente</scenario>
  <assert>Dados apagados. Mensagem confirma acção irreversível</assert>
</evaluation>

<!-- 6. Tools read-only não alteram estado? -->
<evaluation id="6">
  <question>Tools com readOnlyHint=true não modificam dados?</question>
  <scenario>Executar get/list e verificar estado antes e depois</scenario>
  <assert>Estado da BD/sistema idêntico antes e depois da chamada</assert>
</evaluation>

<!-- 7. Paginação e listas grandes funcionam? -->
<evaluation id="7">
  <question>Tools de listagem com muitos resultados retornam dados paginados correctamente?</question>
  <scenario>Lista com 1000+ registos, pedir página 2</scenario>
  <assert>Retorna subset correcto, metadados de paginação presentes</assert>
</evaluation>

<!-- 8. Autenticação/autorização é validada? -->
<evaluation id="8">
  <question>Chamadas sem credenciais válidas são rejeitadas?</question>
  <scenario>Remover variável de ambiente com API key e chamar tool</scenario>
  <assert>Erro claro de autenticação, não crash do servidor</assert>
</evaluation>

<!-- 9. structuredContent é consistente com content textual? -->
<evaluation id="9">
  <question>O campo structuredContent contém os mesmos dados que o texto Markdown?</question>
  <scenario>Comparar campos em structuredContent com conteúdo do text</scenario>
  <assert>Dados idênticos em ambos os formatos</assert>
</evaluation>

<!-- 10. Performance é aceitável? -->
<evaluation id="10">
  <question>A tool responde em menos de 5 segundos em condições normais?</question>
  <scenario>Medir tempo de resposta em 10 chamadas consecutivas</scenario>
  <assert>p95 < 5000ms, sem memory leaks após 100 chamadas</assert>
</evaluation>

Como Executar Evaluations

Método Manual (MCP Inspector)

# Instalar MCP Inspector
npx @modelcontextprotocol/inspector

# Conectar ao MCP local
# Interface web em http://localhost:5173
# Testar cada tool manualmente

Método Automatizado (Script TypeScript, SDK v2 — in-process, sem porta/socket)

handler.fetch do createMcpHandler serve pedidos em processo — o transporte de teste nunca liga de facto ao URL, por isso não há porta a reservar nem servidor a arrancar/parar à volta de cada corrida. Ver testing.md.

// eval/run-evals.ts
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
import { createMcpHandler } from '@modelcontextprotocol/server';
import { createServer } from '../src/server.js';   // a mesma factory usada em src/http.ts

interface EvalResult {
  id: string;
  passed: boolean;
  duration: number;
  error?: string;
}

async function runEval(
  client: Client,
  toolName: string,
  input: Record<string, unknown>,
  assertions: {
    notError?: boolean;
    containsFields?: string[];
    isError?: boolean;
  }
): Promise<EvalResult> {
  const start = Date.now();

  try {
    const result = await client.callTool({ name: toolName, arguments: input });
    const duration = Date.now() - start;

    if (assertions.notError && result.isError) {
      return { id: toolName, passed: false, duration, error: 'Esperava sucesso, recebeu erro' };
    }

    if (assertions.isError && !result.isError) {
      return { id: toolName, passed: false, duration, error: 'Esperava erro, recebeu sucesso' };
    }

    if (assertions.containsFields) {
      const text = (result.content?.[0] as { text?: string })?.text || '';
      for (const field of assertions.containsFields) {
        if (!text.includes(field)) {
          return { id: toolName, passed: false, duration, error: `Campo "${field}" não encontrado` };
        }
      }
    }

    return { id: toolName, passed: true, duration };
  } catch (err) {
    return {
      id: toolName,
      passed: false,
      duration: Date.now() - start,
      error: err instanceof Error ? err.message : String(err)
    };
  }
}

// Executar todas as evaluations
async function main() {
  const handler = createMcpHandler(createServer);
  const transport = new StreamableHTTPClientTransport(new URL('http://test.local/mcp'), {
    fetch: (url, init) => handler.fetch(new Request(url, init)),
  });

  const client = new Client({ name: 'eval-client', version: '1.0.0' }, { versionNegotiation: { mode: 'auto' } });
  await client.connect(transport);

  const results: EvalResult[] = [];

  // Eval 1: Tool básica
  results.push(await runEval(client, 'get_customer', { customer_id: 1 }, {
    notError: true,
    containsFields: ['name', 'email']
  }));

  // Eval 2: Input inválido
  results.push(await runEval(client, 'get_customer', { customer_id: -1 }, {
    isError: true
  }));

  // Eval 3: Recurso inexistente
  results.push(await runEval(client, 'get_customer', { customer_id: 99999 }, {
    isError: true
  }));

  // Sumário
  const passed = results.filter(r => r.passed).length;
  const total = results.length;
  console.log(`\nEvaluations: ${passed}/${total} passou`);
  results.filter(r => !r.passed).forEach(r => {
    console.log(`  FALHOU ${r.id}: ${r.error}`);
  });

  await client.close();
  await handler.close();
  process.exit(passed === total ? 0 : 1);
}

main().catch(console.error);

Adicionar ao package.json:

{
  "scripts": {
    "eval": "tsx eval/run-evals.ts",
    "eval:ci": "npm run build && npm run eval"
  }
}

Checklist de Evaluations (pré-deploy)

  • Eval 1 passa: tool principal com input válido
  • Eval 2 passa: input inválido retorna erro accionável
  • Eval 3 passa: recurso inexistente retorna erro claro
  • Eval 4 passa: idempotência verificada (se aplicável)
  • Eval 5 passa: operações destrutivas confirmadas
  • Eval 6 passa: read-only não altera estado
  • Eval 7 passa: paginação funciona (se aplicável)
  • Eval 8 passa: auth inválida rejeitada graciosamente
  • Eval 9 passa: structuredContent consistente
  • Eval 10 passa: p95 < 5000ms

Integração CI/CD

# .gitea/workflows/eval.yml
name: MCP Evaluations
on: [push, pull_request]

jobs:
  eval:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: npm ci
      - run: npm run eval:ci

evaluation-guide.md v2.0 | 2026-08-19