Files
claude-plugins/infraestrutura/skills/mcp-dev/references/templates.md
T
ealmeida 18e48db490 feat(infraestrutura): mcp-dev v3.0 — SDK TypeScript v2 e transporte HTTP obrigatórios
Regra de Ouro adicionada ao topo da skill: SDK v2 (@modelcontextprotocol/server),
API de alto nível McpServer+registerTool (API de baixo nível Server+setRequestHandler
proibida), transporte HTTP obrigatório (stdio só como excepção documentada), e
proibição explícita de interpolação de dados em comandos shell/SSH.

Motivado por revisão de segurança ao mcp-kivicare (19-08-2026): 8 bugs reais
corrigidos em produção, incluindo injecção de comandos shell crítica e hooks
nativos disparados com o tipo errado. Regras adicionadas para nunca mais acontecer
em silêncio.

- SKILL.md: nova secção Regra de Ouro, API/annotations/capabilities actualizadas
  para v2, transportes HTTP obrigatório, changelog v3.0.0, healing log com os
  3 bugs mais graves do incidente.
- references/templates.md: reescrito para createMcpHandler + createMcpExpressApp
  (HTTP) e serveStdio (excepção), package.json com pacotes v2.
- references/best-practices.md: capabilities/annotations/error handling
  actualizados para API v2, checklist de segurança com regra anti-injecção de
  comandos shell, tabela de transportes com HTTP obrigatório.
- references/evaluation-guide.md: script de evaluations reescrito para
  Client+StreamableHTTPClientTransport in-process (sem porta/socket).
- infraestrutura: 1.2.2 -> 1.3.0
2026-08-19 09:16:31 +01:00

6.4 KiB

MCP Templates - Scaffolding TypeScript (SDK v2, HTTP obrigatório)

Templates de código prontos a usar. Substituir <nome> pelo nome real do MCP. SDK oficial: ts.sdk.modelcontextprotocol.io/v2. Transporte HTTP é obrigatório — o template stdio no fim deste ficheiro só se aplica a uma excepção documentada (ver SKILL.md §Regra de Ouro).


package.json

{
  "name": "mcp-<nome>",
  "version": "1.0.0",
  "type": "module",
  "main": "dist/http.js",
  "scripts": {
    "build": "tsc",
    "start": "node dist/http.js",
    "dev": "tsx src/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/server": "^2.0.0",
    "@modelcontextprotocol/express": "^2.0.0",
    "@modelcontextprotocol/node": "^2.0.0",
    "express": "^4.19.0",
    "zod": "^4.2.0"
  },
  "devDependencies": {
    "@modelcontextprotocol/client": "^2.0.0",
    "@types/node": "^20.0.0",
    "@types/express": "^4.17.0",
    "typescript": "^5.7.0",
    "tsx": "^4.19.0"
  }
}

Node.js 20+ obrigatório. type: module — o SDK v2 é ESM-only na sua API pública (ainda que publique um build CJS de compatibilidade).


tsconfig.json

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "declaration": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "eval"]
}

src/server.ts — factory partilhada (única fonte da definição de tools)

A McpServer do v2 deriva capabilities automaticamente de registerTool/registerResource/registerPrompt — nunca declarar capabilities: {...} à mão (isso é a API de baixo nível v1, proibida).

/**
 * MCP <Nome> — factory partilhada por HTTP e (excepcionalmente) stdio.
 * @author Descomplicar® | @link descomplicar.pt | @copyright 2026
 */

import { McpServer } from '@modelcontextprotocol/server';
import * as z from 'zod/v4';

export function createServer(): McpServer {
  const server = new McpServer({ name: 'mcp-<nome>', version: '1.0.0' });

  server.registerTool(
    'example_get_item',                 // {serviço}_{acção}_{recurso}, snake_case, <=40 chars
    {
      description: 'Obtém um item pelo ID',
      inputSchema: z.object({
        param: z.string().min(1).describe('ID do item'),
      }),
      outputSchema: z.object({
        id: z.string(),
        status: z.string(),
      }),
      annotations: {
        readOnlyHint: true,
        destructiveHint: false,
        idempotentHint: true,
        openWorldHint: false,
      },
    },
    async ({ param }) => {
      // ... lógica aqui — nunca interpolar `param` num comando shell/SQL;
      // se este tool fizer ponte SSH/CLI, ver best-practices.md §Segurança.
      return {
        content: [{ type: 'text', text: `Item: ${param}` }],
        structuredContent: { id: param, status: 'found' },
      };
    },
  );

  return server;
}

src/http.ts — entry point HTTP (obrigatório)

/**
 * MCP <Nome> — HTTP Server (Streamable HTTP)
 * @author Descomplicar® | @link descomplicar.pt | @copyright 2026
 */

import { createMcpExpressApp } from '@modelcontextprotocol/express';
import { toNodeHandler } from '@modelcontextprotocol/node';
import { createMcpHandler } from '@modelcontextprotocol/server';
import { createServer } from './server.js';

const PORT = parseInt(process.env.MCP_HTTP_PORT || '32XX', 10);
const HOST = process.env.MCP_HTTP_HOST || '127.0.0.1';

const handler = createMcpHandler(createServer);
const node = toNodeHandler(handler);

// createMcpExpressApp já valida Host/Origin contra DNS rebinding no bind 127.0.0.1
const app = createMcpExpressApp();

app.all('/mcp', (req, res) => void node(req, res, req.body));

app.get('/health', (_req, res) => {
  res.json({ status: 'ok', name: 'mcp-<nome>', version: '1.0.0' });
});

const httpServer = app.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`);
});

async function shutdown(): Promise<void> {
  await handler.close();
  httpServer.close(() => process.exit(0));
}
process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);

Binding a 0.0.0.0 (exposição fora de localhost) exige passar allowedHosts a createMcpExpressApp({ host: '0.0.0.0', allowedHosts: [...] }) — nunca desligar a validação de Host/Origin.


~/.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/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 (HTTP — obrigatório)

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

src/stdio.ts — apenas para excepção documentada

Usar só quando o host tem de lançar o processo directamente e não existe gateway (justificar no README do MCP). Mesma factory createServer() de src/server.ts — nunca duplicar a definição de tools.

/**
 * MCP <Nome> — stdio (EXCEPÇÃO — ver README §Transporte para a justificação)
 * @author Descomplicar® | @link descomplicar.pt | @copyright 2026
 */

import { serveStdio } from '@modelcontextprotocol/server/stdio';
import { createServer } from './server.js';

void serveStdio(createServer);
console.error('MCP <nome> running on stdio');

::: warning stdout é o canal do protocolo — logar sempre com console.error; um único console.log corrompe o stream JSON-RPC. :::

Configuração ~/.claude.json correspondente:

{
  "mcpServers": {
    "<nome>": {
      "command": "node",
      "args": ["/home/ealmeida/mcp-servers/<nome>/dist/stdio.js"]
    }
  }
}

templates.md v2.0 | 2026-08-19