Skip to main content

mcp-server

Use ao implementar um servidor MCP integrado à API da aplicação. Cobre estrutura do projeto, mapeamento de endpoints OpenAPI para tools MCP, autenticação, transporte (STDIO para Claude Code, SSE para Claude web/desktop), Dockerfile e registro no docker-compose.

Source facts

Repository
marquesmarcelo/ia-dev-team
Last source activity
July 26, 2026 at 19:21
Detected SKILL.md language
Portuguese
Stars
3
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
mcp-server
description
Use ao implementar um servidor MCP integrado à API da aplicação. Cobre estrutura do projeto, mapeamento de endpoints OpenAPI para tools MCP, autenticação, transporte (STDIO para Claude Code, SSE para Claude web/desktop), Dockerfile e registro no docker-compose.
# MCP Server: integração com a API da aplicação > Regras universais do projeto (UUID, stateless, etc.) estão em `CLAUDE.md`. > Esta skill cobre apenas o que é específico da construção de um servidor MCP. ## O que é um MCP server neste contexto Um servidor MCP expõe as funcionalidades da aplicação como **tools** para modelos de linguagem (Claude Code, Claude Desktop, Claude Web). Ele não reimplementa lógica de negócio — chama a API REST já existente da aplicação e adapta o contrato para o protocolo MCP. ``` Claude (LLM) ↓ tool call MCP Server ──→ API REST da aplicação ──→ Banco de dados ↑ openapi.yaml (fonte de verdade) ``` ## Estrutura do projeto ``` mcp/ ├── src/ │ ├── index.ts # entry point — configura servidor e transporte │ ├── tools/ # um arquivo por domínio de ferramenta │ │ ├── processo.ts # tools: criar_processo, listar_processos, etc. │ │ └── usuario.ts │ ├── client/ │ │ └── api.ts # wrapper do fetch para a API — lida com auth e erros │ └── types/ # tipos gerados ou manuais a partir do openapi.yaml ├── Dockerfile ├── Dockerfile.dev ├── package.json └── tsconfig.json ``` ## SDK e dependências (TypeScript — recomendado) ```bash npm install @modelcontextprotocol/sdk zod npm install -D typescript @types/node tsx ``` ```json // package.json { "scripts": { "dev": "tsx watch src/index.ts", "build": "tsc", "start": "node dist/index.js" } } ``` ## Transporte: STDIO vs. SSE | Transporte | Quando usar | Como configurar | |---|---|---| | **STDIO** | Claude Code (linha de comando) | `new StdioServerTransport()` — padrão para dev | | **SSE** | Claude Desktop, Claude Web, K8s | `new SSEServerTransport('/sse', res)` — requer HTTP server | Escolha baseada em `project.config.md` seção "MCP / transporte". Para projetos com K8s, SSE é obrigatório (STDIO não funciona em container acessado remotamente). ## Entry point com STDIO ```typescript // src/index.ts import { Server } from '@modelcontextprotocol/sdk/server/index.js' import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js' import { processoTools, handleProcessoTool } from './tools/processo.js' const server = new Server( { name: process.env.MCP_SERVER_NAME ?? 'mcp-aplicacao', version: '1.0.0' }, { capabilities: { tools: {} } } ) // Registrar todas as tools server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [...processoTools] })) // Rotear chamadas para o handler correto por domínio server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params if (name.startsWith('processo_')) return handleProcessoTool(name, args) throw new Error(`Tool desconhecida: ${name}`) }) const transport = new StdioServerTransport() await server.connect(transport) ``` ## Entry point com SSE (para Claude Desktop / Web / K8s) ```typescript // src/index.ts import express from 'express' import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js' const app = express() const sessions = new Map<string, SSEServerTransport>() app.get('/sse', async (req, res) => { const transport = new SSEServerTransport('/messages', res) const server = createServer() // função que monta o Server com tools sessions.set(transport.sessionId, transport) await server.connect(transport) }) app.post('/messages', express.json(), async (req, res) => { const transport = sessions.get(req.query.sessionId as string) if (!transport) return res.status(404).send('Sessão não encontrada') await transport.handlePostMessage(req, res) }) app.listen(Number(process.env.PORT ?? 3002)) ``` ## Definição de tool (por domínio) ```typescript // src/tools/processo.ts import { z } from 'zod' import { Tool } from '@modelcontextprotocol/sdk/types.js' import { apiClient } from '../client/api.js' // Definição das tools — descrições são o que o LLM lê para decidir quando usar export const processoTools: Tool[] = [ { name: 'processo_criar', description: 'Cria um novo processo administrativo no sistema. Use quando o usuário quiser abrir, registrar ou criar um processo.', inputSchema: { type: 'object', properties: { descricao: { type: 'string', description: 'Descrição detalhada do processo' }, responsavel_id: { type: 'string', format: 'uuid', description: 'UUID do usuário responsável' }, }, required: ['descricao', 'responsavel_id'] } }, { name: 'processo_listar', description: 'Lista processos com filtros opcionais. Use para buscar, consultar ou listar processos.', inputSchema: { type: 'object', properties: { status: { type: 'string', enum: ['aberto', 'em_analise', 'concluido'] }, pagina: { type: 'number', default: 1 }, limite: { type: 'number', default: 20, maximum: 50 } } } } ] // Handler: chama a API e retorna o resultado no formato MCP export async function handleProcessoTool(name: string, args: unknown) { const schema = z.record(z.unknown()) const params = schema.parse(args) switch (name) { case 'processo_criar': const criado = await apiClient.post('/api/v1/processos', params) return { content: [{ type: 'text', text: JSON.stringify(criado, null, 2) }] } case 'processo_listar': const lista = await apiClient.get('/api/v1/processos', params) return { content: [{ type: 'text', text: JSON.stringify(lista, null, 2) }] } default: throw new Error(`Tool não implementada: ${name}`) } } ``` ## Cliente HTTP para a API ```typescript // src/client/api.ts const BASE_URL = process.env.API_BASE_URL ?? 'http://backend:3001' const API_TOKEN = process.env.API_TOKEN // token de serviço, não do usuário final export const apiClient = { async get(path: string, params?: Record<string, unknown>) { const url = new URL(path, BASE_URL) if (params) Object.entries(params).forEach(([k, v]) => v != null && url.searchParams.set(k, String(v)) ) const res = await fetch(url, { headers: { Authorization: `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' } }) if (!res.ok) throw new Error(`API ${path}: ${res.status} ${await res.text()}`) return res.json() }, async post(path: string, body: unknown) { const res = await fetch(new URL(path, BASE_URL), { method: 'POST', headers: { Authorization: `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify(body) }) if (!res.ok) throw new Error(`API ${path}: ${res.status} ${await res.text()}`) return res.json() } } ``` ## Variáveis de ambiente ```bash # .env.example (na pasta mcp/) API_BASE_URL=http://backend:3001 # URL da API — container no docker-compose API_TOKEN= # token de serviço (nunca token de usuário) MCP_SERVER_NAME=mcp-nome-do-sistema PORT=3002 # só para transporte SSE ``` `API_TOKEN` é um token de serviço com permissões específicas — nunca usar token de usuário final, nunca usar `APP_ENV=development` no MCP em produção. ## Dockerfiles ```dockerfile # Dockerfile.dev — STDIO (para usar com Claude Code local) FROM node:20-alpine WORKDIR /app CMD ["sh", "-c", "npm install && npm run dev"] ``` ```dockerfile # Dockerfile — produção SSE (para K8s / Claude Desktop remoto) FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM node:20-alpine WORKDIR /app COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules CMD ["node", "dist/index.js"] ``` ## Registro no docker-compose ```yaml # docker-compose.dev.yml services: mcp: build: context: ./mcp dockerfile: Dockerfile.dev volumes: - ./mcp:/app - /app/node_modules environment: API_BASE_URL: http://backend:3001 API_TOKEN: ${MCP_API_TOKEN} depends_on: - backend # STDIO: não expõe porta # SSE: ports: ["3002:3002"] stdin_open: true # necessário para transporte STDIO tty: true ``` ## Configurar no Claude Code / Claude Desktop Para STDIO (Claude Code): ```json // .claude/mcp-config.json ou ~/.claude/claude_desktop_config.json { "mcpServers": { "nome-do-sistema": { "command": "docker", "args": ["compose", "-f", "docker-compose.yaml", "-f", "docker-compose.dev.yml", "run", "--rm", "mcp"], "env": { "MCP_API_TOKEN": "<token>" } } } } ``` Para SSE (Claude Desktop / Web): ```json { "mcpServers": { "nome-do-sistema": { "url": "http://localhost:3002/sse" } } } ``` ## SDK Python (alternativa) Se `project.config.md` indicar Python como linguagem preferida para o MCP: ```bash pip install mcp httpx ``` ```python # src/main.py import os import httpx from mcp.server import FastMCP mcp = FastMCP(os.getenv("MCP_SERVER_NAME", "mcp-aplicacao")) BASE_URL = os.getenv("API_BASE_URL", "http://backend:3001") TOKEN = os.getenv("API_TOKEN", "") headers = {"Authorization": f"Bearer {TOKEN}"} @mcp.tool() async def processo_criar(descricao: str, responsavel_id: str) -> dict: """Cria um novo processo administrativo.""" async with httpx.AsyncClient() as c: r = await c.post(f"{BASE_URL}/api/v1/processos", json={"descricao": descricao, "responsavel_id": responsavel_id}, headers=headers) r.raise_for_status() return r.json() @mcp.tool() async def processo_listar(status: str | None = None, pagina: int = 1) -> dict: """Lista processos com filtros opcionais.""" async with httpx.AsyncClient() as c: params = {k: v for k, v in {"status": status, "pagina": pagina}.items() if v} r = await c.get(f"{BASE_URL}/api/v1/processos", params=params, headers=headers) r.raise_for_status() return r.json() if __name__ == "__main__": mcp.run() # STDIO por padrão ``` ## Checklist de entrega - [ ] Tools mapeadas a partir de `docs/openapi.yaml` (não inferidas do código) - [ ] Descrições das tools claras para o LLM (o LLM lê a descrição para decidir quando chamar) - [ ] Autenticação via `API_TOKEN` de serviço, não token de usuário - [ ] Transporte correto conforme `project.config.md` (STDIO ou SSE) - [ ] Dockerfile e Dockerfile.dev criados - [ ] Serviço `mcp` registrado no docker-compose - [ ] `.env.example` atualizado com `API_BASE_URL`, `API_TOKEN`, `PORT` - [ ] Configuração de cliente (`mcp-config.json`) documentada em `docs/mcp.md` - [ ] Erros da API tratados e reportados de forma legível ao LLM
View on GitHub