# MCP Templates - Scaffolding TypeScript (SDK v2, HTTP obrigatório) > Templates de código prontos a usar. Substituir `` pelo nome real do MCP. > SDK oficial: [ts.sdk.modelcontextprotocol.io/v2](https://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 ```json { "name": "mcp-", "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- && 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 ```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). ```typescript /** * MCP — 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-', 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) ```typescript /** * MCP — 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-', version: '1.0.0' }); }); const httpServer = app.listen(PORT, HOST, () => { console.log(`MCP HTTP v1.0.0`); console.log(` Endpoint: http://${HOST}:${PORT}/mcp`); console.log(` Health: http://${HOST}:${PORT}/health`); }); async function shutdown(): Promise { 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 ```ini [Unit] Description=MCP HTTP Server After=network.target [Service] Type=simple WorkingDirectory=/home/ealmeida/mcp-servers/mcp- 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) ```json { "mcpServers": { "": { "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. ```typescript /** * MCP — 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 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: ```json { "mcpServers": { "": { "command": "node", "args": ["/home/ealmeida/mcp-servers//dist/stdio.js"] } } } ``` --- *templates.md v2.0 | 2026-08-19*