O Smithery rejeitou o fast-mcp. O Claude levou meia hora pra mover todo o servidor MCP pro SDK oficial — a reescrita saiu barata porque a primeira foi fina.
O projeto smarts integrou MCP seis dias atrás. Pedi pro Claude Code subir três tools usando o gem fast-mcp:
get_contract_info(chain, address) — nome do contrato, tag do adapter de protocolo, contagens de function/eventread_contract_state(chain, address, function_name, args?) — uma única chamada view/pure, cache de 60sget_uniswap_v3_pool(chain, address) — painel completo de V3: preço bidirecional, liquidity, tick, TVL em USDAquele commit foi 27ca82e feat(mcp): serve live contract data over MCP via fast-mcp, 2026-04-21 às 1:37 da manhã. Uns 30 minutos. Saiu redondo.
Seis dias depois, hoje à noite — 2026-04-27 às 20:24 — empurrei outro commit: 4df08fa feat(mcp): switch to official mcp gem with Streamable HTTP transport.
O que aconteceu entre os dois?
Submeti o smarts no Smithery (diretório de MCP servers) para registrar. O scanner deles veio com 405 de cara:
POST /mcp/sse → 405 Method Not Allowed
Levei um tempo pra sacar: fast-mcp 1.6.0 (último release) ainda implementa o HTTP+SSE transport do MCP spec 2024-11-05. Os clientes MCP modernos já migraram pro Streamable HTTP da spec 2025-03-26 — endpoint único, POST + DELETE na mesma URL, sem rota /sse separada.
Ou seja: a spec andou, e o gem dominante na comunidade não acompanhou.
Não ia ficar esperando o fast-mcp. A camada do servidor MCP existe pros clientes consumirem — compatibilidade de spec pesa mais do que qual gem é mais gostoso de escrever.
Troquei fast-mcp pelo gem oficial mcp 0.14.0 da Anthropic. Nessa rodada abri uma thread nova no Amp (rodando Claude Opus por baixo, mesmo que o Claude Code).
O que entrou na migração:
Transport, religação
# Velho: fast-mcp monta automático em /mcp com /sse
# Novo: mount explícito, endpoint único
mount StreamableHTTPTransport.new(server, stateless: true), at: "/mcp"
stateless: true é o pulo do gato. O servidor não segura estado per-session em memória, o que deixa o Puma rodar com workers > 0 e escalar horizontalmente sem sticky session.
Reestruturação da Tool API
Forma velha do fast-mcp:
class GetContractInfoTool < ApplicationTool
arguments do
required(:chain).filled(:string)
required(:address).filled(:string)
end
def call(chain:, address:)
# lógica de negócio
end
end
O gem oficial empurra o encanamento do SDK pra classe base:
class ApplicationTool < MCP::Tool
def self.call(**args, server_context: nil)
hash = payload(**args)
MCP::Tool::Response.new([{ type: "text", text: hash.to_json }])
end
end
class GetContractInfoTool < ApplicationTool
input_schema(
properties: {
chain: { type: "string" },
address: { type: "string" }
},
required: %w[chain address]
)
def self.payload(chain:, address:)
# lógica de negócio, retorna um Hash
end
end
As subclasses só se preocupam com o Hash que payload(**args) retorna; a classe base envelopa isso num text block JSON-encoded dentro de MCP::Tool::Response. Duas vantagens:
Tool.payload(chain:, address:), sem descascar envelope de protocoloO DSL do dry-schema virou literais raw de JSON Schema. Parece um passo pra trás, mas input_schema é o que o cliente lê direto da MCP spec — escrever igual à primitiva da spec é mais honesto.
Mudança dos testes
433 testes, trocados de Tool.new.call(...) pra Tool.payload(...). Tudo verde.
Sincronização de discovery / docs
.well-known/mcp.json com transport: "streamable-http", protocol_version: "2025-03-26"https://smarts.md/mcpclaude mcp add virou de --transport sse pra --transport httpmcp como primary e fast-mcp como legacy/deprecatedA primeira integração de MCP levou 30 minutos. A reescrita, mais ou menos o mesmo. Seis dias entre as duas.
O que me caiu: num ecossistema novo igual ao MCP, os gems atrasam em relação à spec. fast-mcp não é ruim — o mantenedor é ativo. O problema é que o protocolo itera mais rápido do que as implementações de terceiros. Um gem que parece sólido esse ano pode virar "o da spec antiga" daqui a meio ano.
Se eu tivesse escrito dez mil linhas de lógica de negócio em cima do fast-mcp seis meses atrás, hoje estaria preso na spec velha — porque o custo de reescrever seria alto demais pra eu encarar.
Mas o que aconteceu de verdade: a camada fast-mcp sempre foi um wrapper fino. Lógica de negócio morava nas subclasses de ApplicationTool. Então o custo real de reescrever foi:
O caro é a lógica de negócio. Wrappers de protocolo deveriam ser baratos e reescrevíveis. Esse foi o maior favor que o Claude Code me fez seis dias atrás — ele não enfiou a lógica de negócio nos callbacks do fast-mcp. Construiu uma abstração ApplicationTool limpinha que absorveu o SDK. Por isso seis dias depois a troca de SDK precisou só de classe base + schema.
O que me levou de "primeira integração" pra "upgrade de spec e reescrita" em 6 dias é a estabilidade do Claude como modelo subjacente — em qual shell de agent eu estava não importa muito.