- 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>
342 lines
8.9 KiB
Markdown
342 lines
8.9 KiB
Markdown
# 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*
|