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

8.9 KiB

MCP Templates - Scaffolding TypeScript

Templates de código prontos a usar. Substituir <nome> pelo nome real do MCP.


package.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

{
  "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)

#!/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)

#!/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-.service

[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

{
  "mcpServers": {
    "<nome>": {
      "type": "http",
      "url": "http://127.0.0.1:32XX/mcp"
    }
  }
}

templates.md v1.0 | 2026-03-10