- 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>
6.7 KiB
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
finallycom release - Cleanup (ROLLBACK) tem try-catch próprio
crypto.randomBytes()em vez deMath.random()- Secrets em variáveis de ambiente, nunca hardcoded
pnpm auditsem 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