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>
This commit is contained in:
287
infraestrutura/skills/mcp-dev/references/best-practices.md
Normal file
287
infraestrutura/skills/mcp-dev/references/best-practices.md
Normal file
@@ -0,0 +1,287 @@
|
||||
# MCP Best Practices - Referência Completa
|
||||
|
||||
> Extraído de auditorias a 27+ projectos MCP (500+ ferramentas).
|
||||
> Ver também: [PROC-MCP-Desenvolvimento.md](file:///media/ealmeida/Dados/Hub/06-Operacoes/Procedimentos/D7-Tecnologia/MCP/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:**
|
||||
```typescript
|
||||
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:
|
||||
|
||||
```typescript
|
||||
{
|
||||
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()`:**
|
||||
|
||||
```typescript
|
||||
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)
|
||||
|
||||
```typescript
|
||||
// ERRADO: capabilities incompletas -> erro 471
|
||||
capabilities: { tools: {} }
|
||||
|
||||
// CORRECTO: sempre declarar as três, mesmo vazias
|
||||
capabilities: {
|
||||
tools: {},
|
||||
resources: {},
|
||||
prompts: {}
|
||||
}
|
||||
```
|
||||
|
||||
**Handlers mínimos obrigatórios:**
|
||||
```typescript
|
||||
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:
|
||||
|
||||
```typescript
|
||||
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
|
||||
|
||||
```typescript
|
||||
// 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
|
||||
|
||||
```typescript
|
||||
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
|
||||
|
||||
```typescript
|
||||
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
|
||||
|
||||
```typescript
|
||||
// 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:**
|
||||
```bash
|
||||
# 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)
|
||||
|
||||
```typescript
|
||||
// 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*
|
||||
Reference in New Issue
Block a user