Referência MCP privada com Docker em VPS: guia prático
-
Maicon Ramos
- agentes de IA, Claude, Docker, MCP, n8n, VPS
- 10 minutos de leitura
Navegue por tópicos
Esta referência mostra como montar um servidor MCP privado, de leitura, com Docker em uma VPS. Você precisa de VPS Linux administrada, SSH, Docker com Compose e um token aleatório. O custo depende da VPS e do domínio, portanto não é estimado aqui. O tempo também não foi medido. O exemplo não é produção nem integração pronta com Claude ou n8n.
Um servidor MCP conecta clientes de IA a dados, ferramentas e workflows externos. Ele não transforma um modelo em agente confiável. Em uma VPS, entram rede, autenticação, segredos, logs e recuperação. Para uma integração local simples, stdio costuma ter menos risco. Para uma tool persistente e remota, Docker e VPS podem fazer sentido se alguém assumir essa operação.12
A ideia deste guia é a Fronteira de Ação. Cada tool deve explicitar o que o agente pode fazer, com qual identidade, em quais dados e com qual registro. A VPS compra controle operacional. Ela não compra autonomia nem segurança automática.

MCP não é um agente: onde ele entra
O Model Context Protocol é um protocolo aberto para ligar aplicações de IA a fontes externas de dados, ferramentas e workflows. Ele usa mensagens JSON-RPC em UTF-8. O cliente descobre uma tool, entende seu schema e pede que o servidor a execute.111
A arquitetura mínima separa responsabilidades:
cliente compatível → proxy e HTTPS → autenticação → servidor MCP → allowlist e schema → API ou dado → log
Claude Code, ou outro cliente compatível, decide chamar uma ferramenta. O servidor oferece a ferramenta. A API, banco ou workflow executa apenas o trabalho permitido. Um proxy entrega HTTPS, mas não substitui escopo, validação ou auditoria.
stdio local ou HTTP remoto
A especificação MCP define stdio e Streamable HTTP como transportes padrão. stdio liga processos locais pela entrada e saída padrão. Streamable HTTP atende cliente e servidor remotos. Publicar HTTP muda a responsabilidade sobre rede e autorização.2
| Critério | stdio local |
Streamable HTTP em VPS |
|---|---|---|
| Alcance | Cliente e processo na mesma máquina. | Cliente e servidor podem ficar separados. |
| Exposição | Não publica endpoint. | Exige proxy, TLS, firewall e autenticação. |
| Operação | Menos componentes para atualizar. | Inclui domínio, containers, logs e restore. |
| Cenário | Teste, uso individual e tool local. | Automação persistente com responsável operacional. |
Comece com stdio quando estiver validando uma tool, quando há um único usuário ou quando webhook e API existentes já resolvem a integração. HTTP remoto é justificável quando a tool precisa sobreviver ao notebook ou atender cliente remoto. A documentação do MCP diferencia stdio local de fluxos OAuth para servidores HTTP remotos.3
Referência MCP privada com Docker
O exemplo abaixo é didático. Ele expõe uma única tool de leitura, status_operacional, e fica acessível somente em 127.0.0.1 na VPS. Ele serve para revisar Docker, secret, autorização e logs antes de conectar uma API real. Não é servidor de produção, não inclui proxy e não configura Claude Code ou n8n.
1. Crie a pasta e o secret fora do Git
Crie uma pasta restrita. Guarde o token em cofre apropriado e não o cole em Git, logs ou capturas de tela.
mkdir -p ~/mcp-status/secrets
cd ~/mcp-status
umask 077
openssl rand -hex 32 > secrets/mcp_token.txt
printf "secrets/\n" > .gitignore
O Compose permite conceder secrets explicitamente aos serviços. Para aplicações que suportam essa leitura, a documentação do Docker recomenda secrets em vez de variável de ambiente para dados sensíveis.7
2. Salve o servidor de leitura
Crie server.py. O código não acessa banco, shell, ERP nem API. Essa limitação mantém a Fronteira de Ação pequena.
import json
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
TOKEN_PATH = "/run/secrets/mcp_token"
with open(TOKEN_PATH, encoding="utf-8") as secret_file:
TOKEN = secret_file.read().strip()
TOOLS = [{
"name": "status_operacional",
"description": "Retorna o estado não sensível da referência MCP.",
"inputSchema": {"type": "object", "properties": {}, "additionalProperties": False},
}]
def response(request_id, result=None, error=None):
body = {"jsonrpc": "2.0", "id": request_id}
body["error" if error else "result"] = error if error else result
return body
class MCPHandler(BaseHTTPRequestHandler):
def log_message(self, fmt, *args):
print("mcp_http", self.client_address[0], fmt % args, flush=True)
def do_POST(self):
if self.path != "/mcp":
self.send_error(404)
return
if self.headers.get("Authorization") != f"Bearer {TOKEN}":
self._send(401, {"error": "unauthorized"})
return
try:
payload = json.loads(self.rfile.read(int(self.headers["Content-Length"])))
request_id = payload.get("id")
method = payload["method"]
except (KeyError, ValueError, json.JSONDecodeError):
self._send(400, response(None, error={"code": -32600, "message": "Invalid Request"}))
return
if method == "initialize":
result = {"protocolVersion": payload.get("params", {}).get("protocolVersion", "2025-11-25"), "capabilities": {"tools": {}}, "serverInfo": {"name": "mcp-status", "version": "0.1.0"}}
self._send(200, response(request_id, result))
elif method == "tools/list":
self._send(200, response(request_id, {"tools": TOOLS}))
elif method == "tools/call" and payload.get("params", {}).get("name") == "status_operacional":
self._send(200, response(request_id, {"content": [{"type": "text", "text": "status=ok; escopo=leitura"}]}))
else:
self._send(200, response(request_id, error={"code": -32601, "message": "Method not found"}))
def _send(self, status, body):
raw = json.dumps(body).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(raw)))
self.end_headers()
self.wfile.write(raw)
ThreadingHTTPServer(("0.0.0.0", 8080), MCPHandler).serve_forever()
Crie Dockerfile no mesmo diretório:
FROM python:3.12-alpine
WORKDIR /app
COPY server.py /app/server.py
USER 65534
CMD ["python", "/app/server.py"]
3. Use Compose com bind local
Salve como compose.yaml. O bind 127.0.0.1:8080:8080 impede acesso público direto durante a referência.
services:
mcp-server:
build: .
restart: unless-stopped
read_only: true
tmpfs:
- /tmp
ports:
- "127.0.0.1:8080:8080"
secrets:
- source: mcp_token
target: mcp_token
security_opt:
- no-new-privileges:true
secrets:
mcp_token:
file: ./secrets/mcp_token.txt
Suba o container e veja os logs:
docker compose up -d --build
docker compose ps
docker compose logs --tail=50 mcp-server
Container em execução só confirma que o processo iniciou. Não prova backup, TLS ou autorização.8
4. Valide recusa e chamada permitida
Leia o token apenas para montar o cabeçalho no shell atual. A primeira chamada é um POST JSON sem Authorization; pelo código, ela deve devolver 401. As seguintes usam o token lido em $TOKEN. Elas devem devolver JSON-RPC com status_operacional e status=ok; escopo=leitura.
TOKEN="$(<secrets/mcp_token.txt)"
curl -i -X POST http://127.0.0.1:8080/mcp \
-H 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
curl -sS -X POST http://127.0.0.1:8080/mcp \
-H "Authorization: Bearer ${TOKEN}" \
-H 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
curl -sS -X POST http://127.0.0.1:8080/mcp \
-H "Authorization: Bearer ${TOKEN}" \
-H 'Content-Type: application/json' \
--data '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"status_operacional","arguments":{}}}'
Esses resultados são comportamentos esperados do exemplo, não alegações de teste do Runzos. Se a primeira resposta não for 401, pare e revise o controle de acesso. Se as chamadas autorizadas não retornarem os valores descritos, confira docker compose logs mcp-server.
5. Conecte sem abrir a porta pública
Mantenha o serviço privado e abra um túnel SSH a partir da máquina cliente:
ssh -N -L 8080:127.0.0.1:8080 usuario@IP_DA_SUA_VPS
O endpoint local passa a ser http://127.0.0.1:8080/mcp. Use-o apenas em cliente compatível com Streamable HTTP e com a autenticação escolhida. A documentação do Claude Code descreve a conexão a servidores MCP, mas a sintaxe depende da versão instalada. Confirme a documentação atual antes de registrar comandos.10
No n8n, os papéis são diferentes: MCP Client Tool consome servidor externo; MCP Server Trigger expõe um workflow; recursos MCP da instância atendem outro cenário.6 Esta referência não entrega a configuração pronta de nenhum deles.
Segurança e problemas comuns
Prompt injection é LLM01:2025 para a OWASP. Docker, TLS e OAuth resolvem partes distintas do problema. Para ações de maior impacto, use menor privilégio e aprovação humana.9
- Tool com allowlist e schema de entrada estrito.
- Credencial por serviço, com o menor escopo possível.
- Token fora do Git, Compose e logs.
- Proxy HTTPS e firewall antes de qualquer exposição pública.
- Timeout em chamadas externas e logs sem headers de autorização.
- Aprovação humana para ação destrutiva, financeira ou irreversível.
- Backup de configuração e restore testado antes de chamar o processo de recuperação.
| Sintoma | Verificação segura | Correção inicial |
|---|---|---|
| Container reinicia | docker compose logs mcp-server |
Confirme o arquivo do secret e a leitura pelo Docker. |
401 com token |
Compare o token sem imprimi-lo. | Recrie o secret, suba o serviço e reteste no host. |
| Cliente não lista tools | Faça tools/list pelo túnel. |
Corrija rede e autenticação antes de culpar o cliente. |
| Porta pública | Confira firewall e bind. | Remova a regra pública e preserve 127.0.0.1. |
Quando MCP e VPS não são a resposta
MCP padroniza como clientes de IA descobrem e chamam capacidades. Não substitui API, webhook ou n8n. Use API ou webhook quando a integração for determinística. Use n8n quando o workflow visual for o centro da operação. Use MCP quando a interoperabilidade entre cliente e tools for o problema real.
Evite VPS quando o caso ainda for teste local, uma integração de uma etapa já estiver resolvida, a ação for perigosa sem aprovação ou ninguém assumir atualização e restore. Se a operação remota fizer sentido, compare rotas de VPS para automações com custo e requisitos atuais e uma alternativa como a Servla VPS para n8n. Preço, região, SLA e recursos exigem consulta na página vigente.
FAQ
O que é um servidor MCP?
É um servidor que oferece contexto, recursos ou tools para cliente compatível com MCP. O protocolo define como capacidades são descobertas e chamadas.1
Posso rodar MCP em VPS?
Sim, quando há acesso remoto ou automação persistente e alguém assume autenticação, proxy, TLS, firewall, secrets, logs, atualizações e restore.2
MCP substitui API ou webhook?
Não. MCP atende interoperabilidade entre cliente de IA e tools. APIs e webhooks continuam adequados para contratos e eventos determinísticos.
Como proteger MCP remoto?
Reduza a Fronteira de Ação com tools específicas, schema estrito, credenciais limitadas, secrets fora do Git, logs sem segredo e aprovação humana para alto impacto.39
Conclusão
A referência privada é uma forma de revisar os limites antes de conectar sistemas reais. Comece com tool de leitura, mantenha a porta local e amplie o catálogo somente depois de validar escopo, autorização e recuperação. Se decidir operar uma VPS, veja a rota de infraestrutura da Turbo Cloud e escolha conforme a stack, não por promessa de autonomia.















