Setup completo + best practices

TL;DR

Esta nota fecha a trilha com checklist end-to-end para construir e operar MCP servers em produção. Stack base: Python SDK + Pydantic + FastMCP + uvx para distribuição. Roadmap de 4 fases × ~1 semana cada. Best practices distiladas: tool design rigoroso, schemas tipados, audit log, versioning semver, MCP Inspector na CI. Investimento total: ~4 semanas para server interno production-ready.

Todo mundo que constrói um MCP server passa pela mesma tentação: o Inspector mostra a tool funcionando, o time comemora, e o próximo passo “óbvio” é apontar o client de produção pra esse mesmo server ainda sem schema tipado, sem audit log, sem versionamento. É o salto de “funciona no Inspector” para “está em produção para o time” — e é exatamente esse salto que separa um protótipo de fim de semana de um server do qual colegas dependem todo dia. O checklist abaixo é o mapa desse caminho: quatro fases, cada uma com um gate de qualidade que a fase seguinte pressupõe como já resolvido.

Stack recomendada (2026)

┌────────────────────────────────────────────────────────┐
│  Linguagem:       Python 3.11+ (TypeScript alternativa)│
│  SDK:             mcp (FastMCP)                        │
│  Validação:       Pydantic v2                          │
│  Transport:       stdio (local) ou HTTP+SSE (team)     │
│  Hosting (HTTP):  Cloudflare Workers, Fly.io, K8s      │
│  Auth (HTTP):     OAuth 2.1 ou Bearer tokens           │
│  Distribution:    uvx (Python) ou npx (TS)             │
│  Inspector:       MCP Inspector (local + CI)           │
│  Logging:         JSON logs → Loki/CloudWatch         │
│  Monitoring:      Langfuse ou OpenTelemetry           │
└────────────────────────────────────────────────────────┘

Roadmap de 4 fases

gantt
    title Roadmap MCP server production - 4 semanas
    dateFormat  YYYY-MM-DD
    section Fase 1
    Server local básico       :a1, 2026-05-02, 7d
    section Fase 2
    Quality (schemas+errors)  :b1, after a1, 7d
    section Fase 3
    Auth + observability      :c1, after b1, 7d
    section Fase 4
    Deploy + distribution     :d1, after c1, 7d

Fase 1 — Server local (semana 1)

Objetivo: server stdio funcionando.

Checklist

  • pip install mcp ou uv add mcp
  • Estrutura de projeto:
my-mcp-server/
├── pyproject.toml
├── README.md
├── src/
│   └── my_server/
│       ├── __init__.py
│       ├── server.py       # FastMCP setup
│       ├── tools.py        # @mcp.tool() definitions
│       ├── resources.py    # @mcp.resource() definitions
│       └── prompts.py      # @mcp.prompt() definitions
└── tests/
    └── test_tools.py
  • FastMCP(“my-server”) com 1 tool funcionando
  • Test via MCP Inspector: npx @modelcontextprotocol/inspector python -m my_server
  • Plugar em Claude Desktop/Cursor para teste real
  • Configurar logging básico

Exemplo mínimo

# src/my_server/server.py
from mcp.server.fastmcp import FastMCP
from .tools import register_tools
 
mcp = FastMCP("my-server", version="0.1.0")
register_tools(mcp)
 
def main():
    mcp.run()
 
if __name__ == "__main__":
    main()
# src/my_server/tools.py
def register_tools(mcp):
    @mcp.tool()
    def hello(name: str) -> str:
        """Greet a person by name."""
        return f"Hello, {name}!"

Fase 2 — Quality (semana 2)

Objetivo: tools robustos com schemas e error handling.

Checklist

  • Tools com Pydantic models (não primitives)
  • Cada tool com docstring clara: o quê, quando, retorna o quê, quando NÃO usar
  • Erros informativos (raise ValueError com mensagem útil)
  • Output compacto (truncate, paginate)
  • Idempotência onde possível
  • Resources com URI scheme claro
  • Prompts úteis (templates)
  • Tests unitários para cada tool
  • Validação manual via Inspector

Exemplo de tool quality

from pydantic import BaseModel, Field, validator
from typing import Literal
 
class SearchParams(BaseModel):
    query: str = Field(..., min_length=1, max_length=500, description="Search query in natural language")
    limit: int = Field(default=10, ge=1, le=100, description="Max results (1-100)")
    type: Literal["docs", "code", "all"] = Field(default="all")
 
class SearchResult(BaseModel):
    id: str
    title: str
    snippet: str = Field(..., max_length=200)
    url: str
 
@mcp.tool()
def search(params: SearchParams) -> list[SearchResult]:
    """
    Search internal knowledge base.
 
    Use when user asks 'how to', 'what is', 'where can I find'.
    Returns top results with title, snippet, and URL.
 
    Do NOT use for searching code (use search_code instead).
    """
    if not params.query.strip():
        raise ValueError("query cannot be empty or whitespace")
 
    raw = backend.search(params.query, params.limit, params.type)
    return [SearchResult(**r) for r in raw]

Fase 3 — Auth + observability (semana 3)

Objetivo: server pronto para multi-user.

Checklist (HTTP+SSE deploy)

  • Migrar para HTTP+SSE transport
  • Bearer token auth (mínimo) ou OAuth 2.1
  • Per-user scoping em tools (request.user)
  • Audit log estruturado (JSON):
log_entry = {
    "timestamp": iso_now(),
    "user_id": request.user.id,
    "tool": "search",
    "args": sanitize(params.dict()),  # remove PII
    "duration_ms": elapsed,
    "success": True,
    "result_size": len(result)
}
logger.info(json.dumps(log_entry))
  • Rate limiting (slowapi ou custom)
  • Health check endpoint
  • Métricas exportadas (Prometheus, Datadog)
  • Tracing (OpenTelemetry)

Pattern de tool com auth

@mcp.tool()
async def get_my_data(request) -> dict:
    """Get data for the authenticated user."""
    user_id = request.user.id  # extracted by middleware
    return db.query("SELECT * FROM data WHERE user_id = ?", user_id)

Fase 4 — Deploy + distribution (semana 4)

Objetivo: server rodando 24/7 em produção.

Checklist

  • Dockerfile minimal
  • CI/CD pipeline (GitHub Actions, etc.)
  • Deploy: K8s, Fly.io, Cloudflare Workers, ou managed
  • TLS (HTTPS) obrigatório
  • Backup de state (se houver)
  • Monitoring + alertas (Sentry, PagerDuty)
  • Documentação operacional (runbook)
  • Versioning semver começando em 1.0.0
  • CHANGELOG.md
  • Release process documentado

Para servers públicos (extra)

  • README com setup copy-paste
  • Examples folder
  • License (MIT recomendado)
  • Submit ao Awesome MCP Servers
  • Registro em smithery.ai / mcp.so
  • Discord/issues para suporte
  • Versioning rigoroso (breaking = major)

Best practices distiladas

Tool design

Os 7 princípios (resumo)

  1. Nome claro e específico (search_docs, não search)
  2. Descrição como docstring (o que, quando, retorna, quando NÃO)
  3. Inputs tipados com Pydantic
  4. Outputs compactos e estruturados
  5. Erros informativos com sugestão
  6. Sem sobreposição com outras tools
  7. Idempotência quando possível

Ver 03 - Tool design — princípios e categorias.

Schemas

# ❌ Ruim
@mcp.tool()
def query(q: str) -> dict:
    """Query."""
    return db.execute(q)
 
# ✅ Bom
class QueryParams(BaseModel):
    sql: str = Field(..., description="Read-only SQL (SELECT)")
    limit: int = Field(default=100, ge=1, le=1000)
 
@mcp.tool()
def query_database(params: QueryParams) -> dict:
    """
    Run read-only SQL query against production DB.
 
    Use for ad-hoc analysis. Returns up to 1000 rows.
    """
    if not params.sql.strip().upper().startswith("SELECT"):
        raise ValueError("Only SELECT queries allowed")
    return db.execute(params.sql, limit=params.limit)

Versioning

1.0.0 — initial release
1.1.0 — add new tool (backward compatible)
1.1.1 — bug fix
2.0.0 — breaking: rename tool

CHANGELOG documenta migrations.

Testing

# tests/test_tools.py
import pytest
from my_server.tools import search
 
def test_search_basic():
    result = search(SearchParams(query="test"))
    assert len(result) > 0
    assert all(r.url for r in result)
 
def test_search_empty_query():
    with pytest.raises(ValueError, match="cannot be empty"):
        search(SearchParams(query=""))
 
def test_search_limit():
    result = search(SearchParams(query="test", limit=5))
    assert len(result) <= 5

Logging

import logging
import json
 
logger = logging.getLogger("mcp-server")
 
class JSONFormatter(logging.Formatter):
    def format(self, record):
        log_obj = {
            "ts": self.formatTime(record),
            "level": record.levelname,
            "msg": record.getMessage(),
            "module": record.module
        }
        if hasattr(record, "tool_call"):
            log_obj.update(record.tool_call)
        return json.dumps(log_obj)

Logs estruturados → ship to Loki/CloudWatch para analysis.

Armadilhas comuns

Pular a validação no MCP Inspector antes do deploy

O Inspector é a única forma de ver o que o server está expondo antes que o LLM o consuma. Sem ele, você descobre bugs de schema e descrições problemáticas no momento em que o agent chama a tool errada em produção — com usuário esperando. A heurística é simples: toda tool nova, toda mudança de schema, e todo release candidate passa pelo Inspector antes de subir. É 5 minutos de verificação que previnem horas de debugging.

Não incluir o MCP Inspector na pipeline de CI

Se o Inspector só roda localmente na máquina do dev, ele cai no esquecimento quando há pressão de prazo. Integrar o Inspector na CI garante que toda mudança de interface do server seja validada automaticamente — schema inválido, tool sem descrição, resource com URI mal formado falham o build antes de chegar em produção. O comando é simples: npx @modelcontextprotocol/inspector --ci python -m my_server.

Versionar como 0.x indefinidamente

Servers internos que ficam em 0.x por meses são um sinal de que não há disciplina de breaking changes. Quando alguém muda uma tool signature sem bumpar a versão, todos os clients que dependem do contrato anterior quebram silenciosamente. Comece em 1.0.0 quando o server estiver estável o suficiente para outros dependerem, siga semver estritamente (breaking = major), e mantenha um CHANGELOG.md que documente migrations. Versioning não é burocracia — é comunicação com quem usa o server.

Anti-patterns (evite!)

  • -y install sem audit — supply chain risk
  • Tools sem schema — agent passa args errados
  • Output cru (HTML, JSON gigante) — context rot
  • Server gigante (50+ tools) — divida em servers especializados
  • Sem audit log — debugging impossível, compliance impossível
  • Without MCP Inspector na CI — bugs descobertos só em prod
  • Hardcoded credentials — env vars sempre
  • Sem rate limiting (HTTP) — abuse mata budget

Métricas-alvo

MétricaAlvo
Tools por server5-15
Tokens em descrição de tool50-300
Tokens em output médio<2K
Latência tool call (stdio)<100ms
Latência tool call (HTTP)<500ms
Uptime (HTTP server)>99.9%
Audit log coverage100%
% requests com valid schema100% (validação rigorosa)

Quando expandir

SinalPróximo passo
Server tem 30+ toolsQuebra em servers especializados
Múltiplos times consumindoMigra para HTTP+SSE com auth
Compliance entra em jogoAudit log persistente + retenção
Custo cresceRate limiting + caching de outputs
Feedback de usersVersioning rigoroso + CHANGELOG

Como explicar em inglês

A production-ready MCP server is the output of four sequential phases, each with a clear acceptance criterion. Phase 1 gets stdio working with MCP Inspector validation. Phase 2 adds Pydantic schemas, informative errors, compact outputs, and unit tests. Phase 3 migrates to HTTP+SSE with auth, rate limiting, structured audit logging, and observability. Phase 4 deploys with Dockerfile, CI/CD, TLS, monitoring, and semver releases. Each phase builds trust in the server: Phase 1 proves it works, Phase 2 proves it’s robust, Phase 3 proves it’s safe for multiple users, Phase 4 proves it’s operable at scale.

The best practices that matter most distill to: typed schemas so the LLM always passes valid arguments, informative errors so the model can self-correct, compact outputs to avoid context rot, audit logging at 100% coverage, and MCP Inspector in the CI pipeline so regressions are caught before deployment. These aren’t preferences — they’re the difference between a server that works in a demo and one that your team depends on daily.

In a technical interview, you might say:

“I think about MCP server development in four phases. Phase 1 is ‘does it work’ — stdio, FastMCP, Inspector validation. Phase 2 is ‘is it robust’ — Pydantic schemas, informative errors, output pagination, unit tests. Phase 3 is ‘is it safe for a team’ — HTTP+SSE, auth, rate limiting, structured audit logs. Phase 4 is ‘can it be operated’ — Dockerfile, CI/CD, monitoring, semver. Each phase has a clear gate. Most people skip to Phase 4 and wonder why the server is unreliable. The quality work in Phase 2 is what makes everything else work.”

PTEN
Roadmap de implantaçãoDeployment roadmap
Validação de esquemaSchema validation
Tratamento de errosError handling
Paginação de resultadosOutput pagination
Log estruturadoStructured logging
Pipeline de CI/CDCI/CD pipeline
Imagem DockerDocker image
Checklist de releaseRelease checklist
IdempotênciaIdempotency
RetrocompatibilidadeBackward compatibility

Veja também

Referências

O que vem a seguir

Esta nota fecha o galho MCP: você tem o roadmap de 4 fases, os checklists de qualidade e os anti-patterns pra não repetir os erros mais comuns. Mas um MCP server não vive sozinho — ele é consumido por um agente, e esse agente tem as mesmas preocupações de design e custo que apareceram aqui em outra escala. Duas direções naturais a partir daqui:

  • Agentes de Codificação — o MCP server que você acabou de projetar normalmente vira uma tool a mais na caixa de ferramentas de um agente de codificação (Claude Code, Cursor). Entender como esses agentes decidem quando chamar uma tool — e onde eles ainda erram — fecha o loop entre “server bem desenhado” e “agente que usa esse server bem”.
  • Economia de Tokens — cada tool call do seu MCP server consome tokens de contexto do agente que o chama: a descrição da tool, o schema, o output. As mesmas métricas-alvo desta nota (tokens em descrição, tokens em output médio) são, na prática, decisões de economia de tokens — vale a pena entender o orçamento do outro lado da chamada.