Files
Emanuel Almeida 6b3a6f2698 feat: refactor 30+ skills to Anthropic progressive disclosure pattern
- All SKILL.md files now <500 lines (avg reduction 69%)
- Detailed content extracted to references/ subdirectories
- Frontmatter standardised: only name + description (Anthropic standard)
- New skills: brand-guidelines, spec-coauthor, report-templates, skill-creator
- Design skills: anti-slop guidelines, premium-proposals reference
- Removed non-standard frontmatter fields (triggers, version, author, category)

Plugins affected: infraestrutura, marketing, dev-tools, crm-ops, gestao,
core-tools, negocio, perfex-dev, wordpress, design-media

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 15:05:03 +00:00

6.7 KiB

MCP Best Practices - Referência Completa

Extraído de auditorias a 27+ projectos MCP (500+ ferramentas). Ver também: PROC-MCP-Desenvolvimento.md


Nomenclatura de Tools

Padrão: {serviço}_{acção}_{recurso} em snake_case

# Correcto
get_customer_notes
create_project_task
list_invoice_items
delete_session_token

# Errado
getCustomerNotes     (camelCase)
customer-notes-get   (kebab-case)
get_customer_notes_with_all_billing_details_and_history  (>40 chars)

Limites obrigatórios:

  • Tool name: ≤ 40 caracteres
  • Total com prefixo mcp__<servidor>__<tool>: ≤ 64 caracteres

Validação em código:

function validateToolName(name: string): void {
  if (name.length > 40) {
    throw new Error(`Tool "${name}" excede limite de 40 chars (${name.length})`);
  }
}
allTools.forEach(t => validateToolName(t.name));

Annotations Obrigatórias

Cada tool deve declarar as suas annotations para orientar o modelo:

{
  name: 'get_customer',
  description: 'Obtém dados de um cliente pelo ID',
  annotations: {
    readOnlyHint: true,       // Não modifica estado
    destructiveHint: false,   // Não é destrutiva
    idempotentHint: true,     // Mesmo resultado em chamadas repetidas
    openWorldHint: false,     // Opera em dados internos (fechado)
  },
  inputSchema: { ... }
}

Referência rápida:

Annotation Tipo Significado
readOnlyHint boolean Leitura sem efeitos secundários
destructiveHint boolean Pode apagar/substituir dados
idempotentHint boolean Resultado idêntico em múltiplas chamadas
openWorldHint boolean Interage com sistemas externos/web

Inferência automática com inferAnnotations():

import { inferAnnotations } from '@modelcontextprotocol/sdk/server/utils.js';

// Inferir automaticamente a partir do nome e descrição da tool
const annotated = inferAnnotations(tool);

Capabilities Obrigatórias (Regra de Ouro)

// ERRADO: capabilities incompletas -> erro 471
capabilities: { tools: {} }

// CORRECTO: sempre declarar as três, mesmo vazias
capabilities: {
  tools: {},
  resources: {},
  prompts: {}
}

Handlers mínimos obrigatórios:

server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [...] }));
server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [] }));
server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: [] }));
server.setRequestHandler(CallToolRequestSchema, async (req) => { ... });

Formatos de Resposta

Dual Format (JSON + Markdown)

Para máxima compatibilidade, devolver ambos:

return {
  content: [
    {
      type: 'text',
      text: `# Cliente ${data.name}\n\n**ID:** ${data.id}\n**Email:** ${data.email}`
    }
  ],
  structuredContent: {
    id: data.id,
    name: data.name,
    email: data.email,
    status: data.status
  }
};

Erros Accionáveis

// ERRADO: mensagem genérica
throw new Error('Falha ao obter dados');

// CORRECTO: mensagem accionável com contexto e próximos passos
return {
  content: [{
    type: 'text',
    text: [
      `Erro ao obter cliente ID ${customerId}.`,
      `Causa: ${error.message}`,
      `Verificar: 1) ID existe na BD  2) Permissões de acesso  3) Conexão à BD`
    ].join('\n')
  }],
  isError: true
};

Validação com Zod

import { z } from 'zod';

const GetCustomerSchema = z.object({
  customer_id: z.number().int().positive(),
  include_invoices: z.boolean().optional().default(false),
  date_from: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
});

// No handler
const validated = GetCustomerSchema.parse(args);
// Zod lança ZodError automaticamente se inválido

Error Handling

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  try {
    const result = await handleTool(name, args);
    return result;
  } catch (error) {
    if (error instanceof z.ZodError) {
      return {
        content: [{
          type: 'text',
          text: `Parâmetros inválidos:\n${error.errors.map(e => `  - ${e.path.join('.')}: ${e.message}`).join('\n')}`
        }],
        isError: true
      };
    }

    return {
      content: [{
        type: 'text',
        text: `Erro: ${error instanceof Error ? error.message : String(error)}`
      }],
      isError: true
    };
  }
});

Logging

// SEMPRE usar console.error (não console.log — interfere com stdio)
console.error(`[MCP:${toolName}] Início — params: ${JSON.stringify(args)}`);
console.error(`[MCP:${toolName}] Concluído em ${duration}ms`);

Segurança (Checklist Pré-Commit)

  • SQL injection: inputs validados antes de entrar em queries?
  • Interpolação directa em SQL proibida sem validação
  • Transacções em operações multi-query relacionadas
  • Recursos (pool.connect) têm finally com release
  • Cleanup (ROLLBACK) tem try-catch próprio
  • crypto.randomBytes() em vez de Math.random()
  • Secrets em variáveis de ambiente, nunca hardcoded
  • pnpm audit sem vulnerabilidades críticas

Grep de validação:

# Interpolação SQL perigosa
grep -rn '`.*\${.*}`' src/ | grep -i 'select\|insert\|update\|delete\|order'

# Math.random em produção
grep -rn 'Math.random' src/

# connect() sem finally
grep -rn '\.connect()' src/

Padrões SQL (Perfex CRM)

// Perfex usa 0 como "não definido", NÃO NULL
client_id || 0,
project_id || 0,

// Excepção: source em leads precisa ID válido
const [defaultSource] = await db.query(
  'SELECT id FROM tblleads_sources ORDER BY id ASC LIMIT 1'
);
leadSource = source || defaultSource?.id || 1;

Tabelas Perfex (prefixo tbl):

tblclients, tblprojects, tbltasks, tblinvoices,
tblleads, tblleads_sources, tblstaff, tbltaskstimers, tblexpenses

Transportes

Transporte Estado Uso
StreamableHTTP Recomendado Novos MCPs, acesso remoto
stdio Válido Claude Code local, scripts
SSE Deprecated Retrocompatibilidade apenas

Portas HTTP reservadas (3200+):

Porta MCP
3200 outline-postgresql
3201+ disponíveis

Portas SSE reservadas (3100+):

Porta MCP
3100 desk-crm-v3
3101 memory-supabase
3102 wikijs
3103 ssh-unified
3105 n8n
3106 cwp
3107 youtube-research
3108 moloni
3109+ disponíveis

best-practices.md v2.0 | 2026-03-10