| 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)
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
{
"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
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: {} } }
)
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [...processoTools]
}))
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params
if (name.startsWith('processo_')) return handleProcessoTool(name, args)
throw new ()
})
transport = ()
server.(transport)
Entry point com SSE (para Claude Desktop / Web / K8s)
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()
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((process.. ?? ))
Definição de tool (por domínio)
import { z } from 'zod'
import { Tool } from '@modelcontextprotocol/sdk/types.js'
import { apiClient } from '../client/api.js'
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: {
: { : , : [, , ] },
: { : , : },
: { : , : , : }
}
}
}
]
() {
schema = z.(z.())
params = schema.(args)
(name) {
:
criado = apiClient.(, params)
{ : [{ : , : .(criado, , ) }] }
:
lista = apiClient.(, params)
{ : [{ : , : .(lista, , ) }] }
:
()
}
}
Cliente HTTP para a API
const BASE_URL = process.env.API_BASE_URL ?? 'http://backend:3001'
const API_TOKEN = process.env.API_TOKEN
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.()
},
() {
res = ( (path, ), {
: ,
: { : , : },
: .(body)
})
(!res.) ()
res.()
}
}
Variáveis de ambiente
API_BASE_URL=http://backend:3001
API_TOKEN=
MCP_SERVER_NAME=mcp-nome-do-sistema
PORT=3002
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.dev — STDIO (para usar com Claude Code local)
FROM node:20-alpine
WORKDIR /app
CMD ["sh", "-c", "npm install && npm run dev"]
# 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
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
stdin_open: true
tty: true
Configurar no Claude Code / Claude Desktop
Para STDIO (Claude Code):
{
"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):
{
"mcpServers": {
"nome-do-sistema": {
"url": "http://localhost:3002/sse"
}
}
}
SDK Python (alternativa)
Se project.config.md indicar Python como linguagem preferida para o MCP:
pip install mcp httpx
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 {: status, : pagina}.items() v}
r = c.get(, params=params, headers=headers)
r.raise_for_status()
r.json()
__name__ == :
mcp.run()
Checklist de entrega