SSD Nodes Learn Hosting plans →
Guias Matt ConnorPor Matt Connor · Atualizado 2026-08-27

Como rodar servidores MCP em uma VPS para agentes de IA

Configure servidores MCP em uma VPS com stdio e HTTP remoto, systemd, nginx, TLS e autenticação. Evite JSON-RPC corrompido e endpoints expostos sem proteção.

O que você vai construir

Duas configurações MCP funcionais no mesmo VPS. Primeiro, um servidor stdio, uma ferramenta de sistema de ficheiros ou base de dados que o Claude Code inicia como processo filho e com a qual comunica através de um pipe. Depois, um servidor HTTP remoto que funciona como um serviço de rede de longa duração, gerido pelo systemd e protegido por um reverse proxy nginx com TLS. Esse servidor fica acessível a qualquer cliente MCP que seja configurado para o utilizar. A instalação de cada opção é pequena. A maior parte deste guia trata dos dois problemas que realmente causam falhas: manter o fluxo JSON-RPC limpo e nunca expor um endpoint de ferramentas sem autenticação na Internet pública.

O que o MCP realmente é

O Model Context Protocol é uma forma padronizada de um cliente de IA, como Claude Code, Claude Desktop, o Gemini CLI numa VPS ou o seu próprio script, chamar ferramentas externas e ler recursos externos. O próprio modelo não executa nada. Ele faz um pedido ao cliente, o cliente comunica através de JSON-RPC 2.0 com um servidor MCP, o servidor executa a ferramenta e devolve o resultado. Esse cliente é o componente a que as pessoas se referem quando dizem agent harness: o ciclo em torno do modelo que gere a lista de ferramentas, as verificações de permissões e o estado da sessão. O MCP é simplesmente a forma de estender a parte das ferramentas. Existe um único protocolo, por isso um servidor escrito uma vez funciona com todos os clientes que falem MCP. Se esta separação for nova para si, especialmente a questão de como um modelo decide sequer usar uma ferramenta, vale a pena consultar um percurso faseado pelos fundamentos dos agentes durante uma hora antes de fornecer credenciais reais a um destes servidores.

Existem dois transportes, e o restante guia está dividido de acordo com eles:

  • stdio. O cliente inicia o servidor como processo filho e troca mensagens JSON-RPC delimitadas por novas linhas através da entrada e da saída padrão do processo. Não há rede, porta ou autenticação. A própria fronteira de confiança é o processo. Quase todas as ferramentas locais são distribuídas desta forma.
  • Streamable HTTP (e o seu antecessor, HTTP+SSE). O servidor é um serviço web de longa duração. O cliente liga-se através de HTTP, e o servidor pode transmitir as respostas como Server-Sent Events. É assim que partilha um servidor com vários clientes ou executa uma ferramenta que tem de permanecer permanentemente no servidor.

Escolha stdio quando a ferramenta pertence a uma máquina e a um utilizador. Escolha HTTP quando se trata de um serviço partilhado.

Pré-requisitos e limitações importantes

Considere um VPS KVM Ubuntu 24.04 recém-instalado, com root ou sudo. Além disso:

  • Um runtime na linguagem em que o servidor foi desenvolvido. A maioria dos servidores de referência usa Node ou Python. O Ubuntu 24.04 inclui o Node 18, mas vários pacotes MCP atuais exigem o Node 20 ou mais recente. Por isso, instale uma versão LTS atual a partir do NodeSource ou use nvm, em vez de confiar no apt. O Python 3.12 já está instalado.
  • Um domínio e um registo DNS A, mas apenas para o servidor HTTP remoto. O TLS requer um nome que resolva para este VPS. O exemplo stdio não requer DNS.
  • 512 MB de RAM são suficientes. Os servidores MCP são processos JSON-RPC leves. O consumo de memória depende do que a ferramenta utiliza, como um driver de base de dados ou uma cache de ficheiros, e não do protocolo.
  • A especificação é recente e continua a mudar. A revisão de 2025-03-26 substituiu HTTP+SSE por Streamable HTTP e marcou SSE como obsoleto. SSE continua a funcionar e muitos servidores ainda o utilizam. Por isso, trate qualquer definição fixa de transporte como algo que deve ser confirmado novamente nas notas de versão do servidor, e não como uma regra definitiva.

Etapa 1: ligue um servidor stdio ao Claude Code

Comece pelo servidor de sistema de ficheiros. Ele é oficial, tem manutenção ativa e precisa apenas do Node. O comando abaixo regista-o no Claude Code e limita o âmbito ao projeto atual, para que a configuração fique num ficheiro que pode ser guardado no repositório:

cd /home/matt/projects/api
claude mcp add --scope project --transport stdio filesystem \
  -- npx -y @modelcontextprotocol/server-filesystem /home/matt/projects/api

O separador -- é importante: tudo depois dele é o comando que o Claude Code executará, não uma opção do Claude Code. Isso cria um ficheiro .mcp.json na raiz do projeto:

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/home/matt/projects/api"
      ]
    }
  }
}

Nada está a ser executado ainda. Quando iniciar novamente o Claude Code neste diretório, o agente lê .mcp.json, inicia npx -y @modelcontextprotocol/server-filesystem ... como processo filho e realiza o handshake MCP através da entrada e saída padrão desse processo. Confirme se a configuração foi aplicada:

claude mcp list

Um servidor em funcionamento apresenta o comando e um visto verde, filesystem: npx -y @modelcontextprotocol/server-filesystem ... - ✓ Connected. Dentro da sessão, o comando slash /mcp lista as ferramentas que o servidor disponibiliza (read_file, write_file, list_directory), e o agente pode agora chamá-las nos caminhos que autorizou. Uma ferramenta de base de dados segue o mesmo formato: substitua o pacote e passe uma cadeia de ligação como argumento final. No entanto, consulte o repositório do próprio servidor para confirmar o nome atual do pacote, porque o servidor de referência para Postgres mudou de responsável mais de uma vez.

Este é o objetivo de executar o agente no próprio servidor: a sessão do Claude Code é executada no VPS dentro do tmux, e os servidores stdio são executados junto dela, com acesso direto aos ficheiros do projeto e aos serviços locais, sem uma ida e volta pela rede. Quando o agente tem write_file e read_file, vale a pena associar esse alcance a uma skill que o oriente para a menor alteração que resolva o problema, porque uma ferramenta de sistema de ficheiros torna uma reescrita extensa tão barata como uma correção de duas linhas. A mesma ligação pode ultrapassar os ficheiros locais: se já executar um motor de pesquisa no VPS, pode entregar ao agente a sua própria instância SearXNG como ferramenta de pesquisa, mantendo as consultas no seu servidor, mas introduzindo diretamente no contexto do agente o texto não confiável das páginas, sobre o qual ele atuará em seguida.

Etapa 2: criar um servidor HTTP remoto

Um servidor stdio termina com o processo-pai e é iniciado uma vez por cliente. Por isso, se executar duas sessões do Claude Code no servidor que trocam trabalho entre si, cada uma terá a sua própria cópia privada da ferramenta. Quando precisa de uma ferramenta que permaneça ativa para todos os clientes, de uma ferramenta de operações partilhada, de um gateway de base de dados ou de algo que o seu portátil e o seu CI possam utilizar, precisa do transporte HTTP e de um serviço real. Este é um servidor Python mínimo que utiliza o SDK oficial e expõe uma ferramenta:

# /opt/mcp-ops/server.py
from mcp.server.fastmcp import FastMCP
import subprocess

mcp = FastMCP("ops-tools", host="127.0.0.1", port=8000)

@mcp.tool()
def disk_free() -> str:
    """Return `df -h` for the server."""
    out = subprocess.run(["df", "-h"], capture_output=True, text=True)
    return out.stdout

if __name__ == "__main__":
    # Serves Streamable HTTP at /mcp on 127.0.0.1:8000
    mcp.run(transport="streamable-http")

Nota host="127.0.0.1". O servidor faz bind apenas em localhost. Nada fora do servidor pode aceder-lhe diretamente. Isto é exatamente o que pretende antes de existir autenticação. Instale-o no seu próprio virtualenv para que o systemd tenha um caminho estável para o interpretador:

sudo useradd --system --home /opt/mcp-ops --shell /usr/sbin/nologin mcp
sudo install -d -o mcp -g mcp /opt/mcp-ops
sudo -H -u mcp python3 -m venv /opt/mcp-ops/.venv
sudo -H -u mcp /opt/mcp-ops/.venv/bin/pip install "mcp[cli]"

Etapa 3: mantenha o serviço ativo com systemd

Uma ferramenta indisponível quando o agente precisa dela é pior do que não ter ferramenta nenhuma. Isto é especialmente importante quando o próprio cliente é um processo de longa duração: um agente sempre ativo que mantém a memória e os agendamentos entre reboots chamará estas ferramentas conforme o agendamento, sem ninguém a acompanhar, por isso o servidor também tem de voltar a iniciar sozinho. Escreva /etc/systemd/system/mcp-ops.service:

[Unit]
Description=MCP ops-tools server
After=network.target

[Service]
Type=simple
User=mcp
WorkingDirectory=/opt/mcp-ops
ExecStart=/opt/mcp-ops/.venv/bin/python /opt/mcp-ops/server.py
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true

[Install]
WantedBy=multi-user.target

O caminho absoluto para o Python do venv em ExecStart não é opcional. Aponte-o para /usr/bin/python3 e o processo será iniciado com ModuleNotFoundError: No module named 'mcp', porque o interpretador do sistema nunca viu o seu pip install. Ative e verifique:

sudo systemctl daemon-reload
sudo systemctl enable --now mcp-ops
sudo systemctl status mcp-ops
curl -si -H 'Accept: application/json, text/event-stream' \
  -H 'Content-Type: application/json' \
  -X POST http://127.0.0.1:8000/mcp

status deve apresentar active (running). curl devolve HTTP/1.1 400 Bad Request com um erro JSON-RPC no corpo, porque o pedido não tinha sessão nem um payload JSON válido. É exatamente isso que pretende: prova que a porta responde e fala o protocolo. Connection refused ou uma resposta vazia significa que o processo não está associado ao endereço esperado; consulte journalctl -u mcp-ops -n 50.

Passo 4: coloque o TLS e um reverse proxy na frente

O servidor escuta em localhost. Para o alcançar a partir de qualquer local, termine o TLS no nginx e faça proxy para o serviço interno. Instale o nginx, obtenha um certificado com Certbot e Let's Encrypt no nginx e, em seguida, escreva o bloco location. A parte crítica é desativar o buffering, porque o comportamento predefinido do nginx mantém uma resposta em espera até estar completa. Isso bloqueia um fluxo SSE indefinidamente:

server {
    listen 443 ssl;
    server_name mcp.example.com;

    # ssl_certificate lines managed by Certbot

    location /mcp {
        proxy_pass http://127.0.0.1:8000;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;

        # The four lines that make SSE work through nginx:
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 3600s;
        chunked_transfer_encoding off;
    }
}

Recarregue com sudo nginx -t && sudo systemctl reload nginx. Se já executar vários contentores, o mesmo trabalho é feito pelo reverse proxy Traefik com TLS automático. Ele emite o certificado e encaminha o tráfego com base no nome do host. Basta adicionar labels ao contentor MCP. Em qualquer dos casos, o reverse proxy passa a ser o único componente numa porta pública e encaminha o tráfego para um serviço que ainda não está protegido. Corrija isso antes de registar o URL em qualquer local.

Etapa 5: a regra de segurança que domina este tema

Nunca exponha um endpoint MCP sem autenticação. Um servidor MCP não é uma API apenas de leitura. Ele concede acesso a ferramentas, aos seus ficheiros, à sua base de dados e, por vezes, a uma shell. Um /mcp aberto na Internet pública permite que um desconhecido tenha o mesmo alcance que o seu agente de IA: o atacante lista as suas ferramentas e depois chama-as. Trate-o exatamente como um socket administrativo sem autenticação, porque é isso que ele é. O que um token roubado permite fazer também depende do servidor que está por trás dele: o servidor MCP apenas de leitura fornecido com o openGym, o monitorizador de treinos só pode devolver dados de treino, enquanto uma ferramenta de sistema de ficheiros ou de shell dá acesso ao servidor.

Três defesas, por ordem de preferência:

  1. Não o publique. Mantenha o servidor em 127.0.0.1 e aceda-lhe a partir do seu portátil através de um túnel SSH: ssh -L 8000:127.0.0.1:8000 matt@vps; depois aponte o cliente para http://127.0.0.1:8000/mcp. Nada fica exposto.
  2. Coloque-o numa rede privada. Associe o endereço do túnel de uma VPN WireGuard auto-hospedada e permita que apenas os peers da VPN lhe acedam. A Internet pública vê uma porta fechada.
  3. Se tiver de ser público, exija um token. A opção correta é o fluxo OAuth do MCP, que o transporte HTTP suporta nativamente. O mínimo pragmático é um token bearer partilhado, validado no proxy. É barato e bloqueia completamente as tentativas de acesso oportunistas:
location /mcp {
    if ($http_authorization != "Bearer REPLACE_WITH_LONG_RANDOM") {
        return 401;
    }
    proxy_pass http://127.0.0.1:8000;
    # ...buffering-off block from above...
}

Gere o token com openssl rand -hex 32 e nunca associe o próprio servidor a 0.0.0.0 sem uma destas proteções à frente. O cliente envia depois o token como cabeçalho. No Claude Code:

claude mcp add --scope project --transport http ops-tools https://mcp.example.com/mcp \
  --header 'Authorization: Bearer ${MCP_TOKEN}'

Defina MCP_TOKEN na sua shell para que o segredo nunca seja gravado em .mcp.json em texto simples. O Claude Code expande ${MCP_TOKEN} a partir do ambiente no momento da leitura.

Todas as defesas acima protegem o endpoint, não o agente que já possui o token. Essa é a outra metade do problema: se o seu cliente for o DeepSeek Harness, plugins que controlam as ferramentas que um agente pode chamar e analisam a saída das ferramentas em busca de instruções injetadas cobrem esse lado.

Passo 6: depure com o MCP Inspector

Quando um servidor se comportar de forma inesperada, não tente adivinhar a causa a partir do agente. Teste-o diretamente com o Inspector, o cliente de teste oficial baseado na Web. Para um servidor stdio, forneça-lhe o mesmo comando que o agente executa:

npx @modelcontextprotocol/inspector \
  npx -y @modelcontextprotocol/server-filesystem /tmp

É iniciada uma interface em http://localhost:6274 (as versões recentes mostram um URL com uma cadeia de consulta MCP_PROXY_AUTH_TOKEN; use essa ligação exata ou a interface rejeita a ligação) e um proxy na porta 6277. Clique em Connect, depois em List Tools e, por fim, em Call Tool, usando argumentos reais. Se funcionar no Inspector mas falhar no agente, o problema está na configuração do cliente, não no servidor. Para o servidor HTTP remoto, selecione o transporte Streamable HTTP, introduza https://mcp.example.com/mcp, adicione o cabeçalho Authorization e estabeleça a ligação. Esta é a forma mais rápida de confirmar que a autenticação e o proxy estão corretos antes de envolver qualquer agente.

Manter os servidores atualizados

O MCP evolui rapidamente, por isso aplique as correções segundo um calendário. Os servidores Node iniciados com npx -y obtêm a versão mais recente a cada criação do processo. Isto é conveniente, mas não permite reproduzir o mesmo ambiente. Fixe a versão exata que testou, leia-a em npm view @modelcontextprotocol/server-filesystem version e acrescente-a ao nome do pacote em .mcp.json (@modelcontextprotocol/server-filesystem@<version>) assim que o servidor se tornar importante. Depois, atualize-a de forma deliberada. Os servidores Python geridos pelo systemd são atualizados com sudo -H -u mcp /opt/mcp-ops/.venv/bin/pip install -U "mcp[cli]", seguido de sudo systemctl restart mcp-ops. Ao atualizar, monitorize a revisão da especificação visada pelo seu SDK. Uma mudança entre SSE e Streamable-HTTP pode alterar o transporte que os seus clientes têm de solicitar.

Modos de falha e as mensagens apresentadas

O agente indica que o servidor falhou. claude mcp list apresenta ✗ Failed to connect, e a TUI indica MCP server 'filesystem' failed to start. Execute claude --debug. Normalmente verá Error: spawn npx ENOENT, porque o comando não está no PATH do agente. Falta o runtime ou ele não está no local onde o agente o procura: Node não está instalado, npx está ausente ou foi referenciado um Python de virtualenv por um nome simples. Corrija o comando para usar um caminho absoluto ou instale o runtime. Depois, estabeleça novamente a ligação.

Um servidor stdio estabelece a ligação e desconecta imediatamente. O cliente regista um erro de análise de JSON, como Unexpected token 'S', "Server sta"... is not valid JSON ou Failed to parse message. A causa é sempre a mesma: o servidor escreveu uma linha de log em stdout. Em stdio, stdout é o canal JSON-RPC. Qualquer texto adicional corrompe o fluxo e interrompe o handshake. Em Node, console.log escreve em stdout; use console.error. Em Python, um print() simples escreve em stdout. Escreva os logs com logging configurado para sys.stderr ou passe file=sys.stderr. A regra é absoluta: em stdio, apenas JSON-RPC pode ser escrito em stdout. Todo o texto destinado a pessoas deve ser escrito em stderr.

Um servidor remoto excede o tempo limite ou fecha a ligação durante o handshake. O cliente falha com MCP error -32000: Connection closed, ou o Inspector fica bloqueado em Connect e nunca apresenta as ferramentas. Atrás do nginx, isto é causado pelo buffering: o proxy mantém o fluxo SSE em vez de o enviar imediatamente. Assim, o cliente fica à espera de uma resposta que nunca chega. Adicione proxy_buffering off;, juntamente com o restante bloco do Step 4, ao location. Confirme com curl -N usando o URL público. Os dados dos eventos devem chegar de forma incremental, e não todos de uma vez no fim.

A autenticação é rejeitada. O cliente apresenta Error POSTing to endpoint (HTTP 401) ou simplesmente 401 Unauthorized. O cabeçalho pode estar ausente, o token pode estar incorreto ou a variável de shell pode estar vazia quando o cliente leu a configuração. Esta é uma armadilha comum, porque ${MCP_TOKEN} não produz qualquer valor se a variável não estiver definida. Nesse caso, o nginx vê Bearer sem valor. Mostre o valor da variável, adicione novamente o cabeçalho e confirme que os bytes são exatamente iguais aos do token no if do nginx.

O serviço não inicia no systemd. journalctl -u mcp-ops apresenta ModuleNotFoundError: No module named 'mcp', e ExecStart aponta para o Python do sistema em vez do interpretador do venv. Ou Address already in use indica que outro processo está a usar a porta 8000. Encontre-o com sudo ss -ltnp | grep 8000.

FAQ

O que é exatamente um servidor MCP?

É um programa que expõe ferramentas e recursos a um cliente de IA através do Model Context Protocol, utilizando JSON-RPC 2.0. O modelo de IA nunca executa diretamente a ferramenta. Pede ao seu cliente, o cliente chama o servidor MCP, e o servidor executa a operação e devolve um resultado. Como o protocolo é padrão, um servidor funciona com qualquer cliente compatível, seja Claude Code, Claude Desktop ou Gemini CLI.

Qual é a diferença entre o transporte stdio e HTTP?

Um servidor stdio é iniciado pelo cliente como um processo filho e comunica através de stdin/stdout. Por isso, existe apenas enquanto um cliente estiver em execução numa máquina e não precisa de rede nem de autenticação. Um servidor HTTP é um serviço de rede de longa duração que pode ser acedido simultaneamente por vários clientes. Por esse motivo, requer TLS e autenticação. Use stdio para ferramentas locais de utilizador único. Use HTTP (Streamable HTTP nos servidores atuais) para qualquer serviço partilhado ou persistente.

Como protejo um servidor MCP remoto?

Parta do princípio de que ele concede acesso às suas ferramentas, ficheiros, base de dados ou shell. Nunca o exponha sem autenticação. O melhor é mantê-lo associado a localhost e aceder-lhe através de um túnel SSH ou de uma VPN privada. Se tiver de ser público, coloque-o atrás de um reverse proxy que imponha um bearer token ou o fluxo OAuth do MCP. Gere o token com openssl rand -hex 32 e nunca associe o servidor a 0.0.0.0 sem uma destas proteções à frente.

Como depuro um servidor que não inicia?

Primeiro, verifique claude mcp list e ✗ Failed to connect com spawn ... ENOENT. Isso significa que o comando ou o runtime está em falta. Corrija o caminho ou instale-o. Se o servidor estabelecer a ligação e depois a perder com um erro de análise JSON, está a escrever logs em stdout e a corromper o fluxo JSON-RPC. Mova todos os logs para stderr. Para qualquer outro problema, execute o comando exato no MCP Inspector. Esta ferramenta executa o servidor isoladamente, permitindo distinguir um erro do servidor de um erro na configuração do cliente.