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:
2026-03-12 15:05:03 +00:00
parent 9404af7ac9
commit 6b3a6f2698
397 changed files with 67154 additions and 17257 deletions

View 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*

View File

@@ -0,0 +1,280 @@
# 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:
```xml
<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:
```xml
<!-- 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)
```bash
# 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)
```typescript
// eval/run-evals.ts
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
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;
// Verificar assertions
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]?.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 transport = new StdioClientTransport({
command: 'node',
args: ['dist/index.js']
});
const client = new Client({ name: 'eval-client', version: '1.0.0' });
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();
process.exit(passed === total ? 0 : 1);
}
main().catch(console.error);
```
**Adicionar ao package.json:**
```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
```yaml
# .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 v1.0 | 2026-03-10*

View File

@@ -0,0 +1,341 @@
# MCP Templates - Scaffolding TypeScript
> Templates de código prontos a usar. Substituir `<nome>` pelo nome real do MCP.
---
## package.json
```json
{
"name": "mcp-<nome>",
"version": "1.0.0",
"type": "module",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"start:http": "node dist/index-http.js",
"dev": "tsx src/index.ts",
"dev:http": "tsx src/index-http.ts",
"eval": "tsx eval/run-evals.ts",
"eval:ci": "npm run build && npm run eval",
"reload:http": "npm run build && systemctl --user restart mcp-<nome> && sleep 2 && curl -s http://127.0.0.1:32XX/health"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0",
"zod": "^3.22.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.0.0",
"tsx": "^4.0.0"
}
}
```
---
## tsconfig.json
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "eval"]
}
```
---
## src/index.ts (stdio — padrão local)
```typescript
#!/usr/bin/env node
/**
* MCP <Nome>
* @author Descomplicar® | @link descomplicar.pt | @copyright 2026
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ListResourcesRequestSchema,
ListPromptsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
// --- Schemas de validação ---
const ExampleSchema = z.object({
param: z.string().min(1, 'param é obrigatório'),
});
// --- Definição de tools ---
const allTools = [
{
name: 'example_get_item', // {serviço}_{acção}_{recurso}
description: 'Obtém um item pelo ID',
annotations: {
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false,
},
inputSchema: {
type: 'object' as const,
properties: {
param: { type: 'string', description: 'ID do item' },
},
required: ['param'],
},
handler: async (args: unknown) => {
const { param } = ExampleSchema.parse(args);
// ... lógica aqui
return {
content: [{ type: 'text' as const, text: `Item: ${param}` }],
structuredContent: { id: param, status: 'found' },
};
},
},
];
// --- Servidor ---
const server = new Server(
{ name: 'mcp-<nome>', version: '1.0.0' },
{ capabilities: { tools: {}, resources: {}, prompts: {} } }
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: allTools.map(t => ({
name: t.name,
description: t.description,
annotations: t.annotations,
inputSchema: t.inputSchema,
})),
}));
server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [] }));
server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: [] }));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const tool = allTools.find(t => t.name === name);
if (!tool) {
return {
content: [{ type: 'text', text: `Tool desconhecida: ${name}` }],
isError: true,
};
}
try {
return await tool.handler(args);
} 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 em ${name}: ${error instanceof Error ? error.message : String(error)}`,
}],
isError: true,
};
}
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('MCP <nome> running on stdio');
}
main().catch(console.error);
```
---
## src/index-http.ts (StreamableHTTP — padrão remoto)
```typescript
#!/usr/bin/env node
/**
* MCP <Nome> - HTTP Server Mode
* @author Descomplicar® | @link descomplicar.pt | @copyright 2026
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import {
ListToolsRequestSchema,
CallToolRequestSchema,
ListResourcesRequestSchema,
ListPromptsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import * as http from 'http';
import { URL } from 'url';
import { randomUUID } from 'crypto';
const PORT = parseInt(process.env.MCP_HTTP_PORT || '3200', 10);
const HOST = process.env.MCP_HTTP_HOST || '127.0.0.1';
const STATEFUL = process.env.MCP_STATEFUL !== 'false';
const sessions = new Map<string, { transport: StreamableHTTPServerTransport }>();
// Importar allTools do mesmo local que stdio
import { allTools } from './tools/index.js';
function createMcpServer(): Server {
const server = new Server(
{ name: 'mcp-<nome>', version: '1.0.0' },
{ capabilities: { tools: {}, resources: {}, prompts: {} } }
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: allTools.map(t => ({
name: t.name,
description: t.description,
annotations: t.annotations,
inputSchema: t.inputSchema,
})),
}));
server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [] }));
server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: [] }));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const tool = allTools.find(t => t.name === name);
if (!tool) {
return { content: [{ type: 'text', text: `Tool desconhecida: ${name}` }], isError: true };
}
try {
return await tool.handler(args);
} catch (error) {
return {
content: [{ type: 'text', text: `Erro: ${error instanceof Error ? error.message : String(error)}` }],
isError: true,
};
}
});
return server;
}
async function main() {
const httpServer = http.createServer(async (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Mcp-Session-Id');
if (req.method === 'OPTIONS') { res.writeHead(200); res.end(); return; }
const url = new URL(req.url || '/', `http://${HOST}:${PORT}`);
if (url.pathname === '/health') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'ok', version: '1.0.0', sessions: sessions.size, tools: allTools.length }));
return;
}
if (url.pathname === '/mcp') {
try {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: STATEFUL ? () => randomUUID() : undefined,
});
const srv = createMcpServer();
if (STATEFUL && transport.sessionId) {
sessions.set(transport.sessionId, { transport });
transport.onclose = () => { if (transport.sessionId) sessions.delete(transport.sessionId); };
}
await srv.connect(transport);
await transport.handleRequest(req, res);
} catch (error) {
if (!res.headersSent) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Internal server error' }));
}
}
return;
}
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Not found' }));
});
httpServer.listen(PORT, HOST, () => {
console.log(`MCP <nome> HTTP v1.0.0`);
console.log(` Endpoint: http://${HOST}:${PORT}/mcp`);
console.log(` Health: http://${HOST}:${PORT}/health`);
console.log(` Mode: ${STATEFUL ? 'Stateful' : 'Stateless'}`);
});
const shutdown = () => httpServer.close(() => process.exit(0));
process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);
}
main().catch(console.error);
```
---
## ~/.config/systemd/user/mcp-<nome>.service
```ini
[Unit]
Description=MCP <Nome> HTTP Server
After=network.target
[Service]
Type=simple
WorkingDirectory=/home/ealmeida/mcp-servers/mcp-<nome>
ExecStart=/home/ealmeida/.nvm/versions/node/v22.18.0/bin/node dist/index-http.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
Environment=MCP_HTTP_PORT=32XX
Environment=LOG_LEVEL=error
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=default.target
```
---
## Configuração ~/.claude.json
```json
{
"mcpServers": {
"<nome>": {
"type": "http",
"url": "http://127.0.0.1:32XX/mcp"
}
}
}
```
---
*templates.md v1.0 | 2026-03-10*