Smithery rechazó fast-mcp. Claude trasladó todo el servidor MCP al SDK oficial en media hora — la reescritura salió barata porque la primera capa era fina.
El proyecto smarts integró MCP hace seis días. Le pedí a Claude Code que pusiera en marcha tres tools usando el gem fast-mcp:
get_contract_info(chain, address) — nombre del contrato, etiqueta del adapter de protocolo, conteos de function/eventread_contract_state(chain, address, function_name, args?) — una sola llamada view/pure, caché de 60sget_uniswap_v3_pool(chain, address) — panel completo de V3: precio bidireccional, liquidity, tick, TVL en USDAquel commit fue 27ca82e feat(mcp): serve live contract data over MCP via fast-mcp, 2026-04-21 a la 1:37 AM. Unos 30 minutos. Salió fluido.
Seis días después, esta noche — 2026-04-27 a las 20:24 — empujé otro commit: 4df08fa feat(mcp): switch to official mcp gem with Streamable HTTP transport.
¿Qué pasó entre medias?
Envié smarts a Smithery (un directorio para servidores MCP) para registrarlo. Su escáner devolvió 405 de inmediato:
POST /mcp/sse → 405 Method Not Allowed
Tardé un rato en entenderlo: fast-mcp 1.6.0 (la última release) sigue implementando el HTTP+SSE transport del MCP spec 2024-11-05. Los clientes MCP modernos ya se han movido al Streamable HTTP del spec 2025-03-26 — un único endpoint, POST + DELETE en la misma URL, sin un /sse aparte.
Es decir: el spec avanzó, y el gem dominante de la comunidad no se puso al día.
No iba a esperar a fast-mcp. La capa del servidor MCP existe para que los clientes la consuman — la compatibilidad con el spec pesa más que qué gem se siente más cómodo.
Sustituí fast-mcp por el gem oficial mcp 0.14.0 de Anthropic. Para esta vuelta abrí un thread nuevo en Amp (con Claude Opus por debajo, igual que Claude Code).
Lo que aterrizó en la migración:
Transport, recableado
# Viejo: fast-mcp se mont automáticamente en /mcp con /sse
# Nuevo: mount explícito, endpoint único
mount StreamableHTTPTransport.new(server, stateless: true), at: "/mcp"
stateless: true es la clave. El servidor no guarda estado per-session en memoria, lo que permite a Puma correr con workers > 0 y escalar horizontalmente sin sticky sessions.
Tool API reestructurada
La forma vieja con fast-mcp:
class GetContractInfoTool < ApplicationTool
arguments do
required(:chain).filled(:string)
required(:address).filled(:string)
end
def call(chain:, address:)
# lógica de negocio
end
end
El gem oficial empuja la fontanería del SDK a la clase 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 negocio, devuelve un Hash
end
end
Las subclases solo se ocupan del Hash que devuelve payload(**args); la clase base lo envuelve en un text block JSON-encoded dentro de MCP::Tool::Response. Dos ventajas:
Tool.payload(chain:, address:), sin desenvolver el envelope de protocoloEl DSL de dry-schema lo cambié por literales raw de JSON Schema. Parece un paso atrás, pero input_schema es lo que los clientes leen literalmente del MCP spec — escribirlo como primitiva del spec es más honesto.
Tests, mudanza
433 tests, pasados de Tool.new.call(...) a Tool.payload(...). Todos en verde.
discovery / docs sincronizados
.well-known/mcp.json con transport: "streamable-http", protocol_version: "2025-03-26"https://smarts.md/mcpclaude mcp add cambiado de --transport sse a --transport httpmcp como primary y fast-mcp como legacy/deprecatedLa primera integración de MCP me llevó 30 minutos. La reescritura, parecido. Seis días entre ambas.
Lo que me cayó: en un ecosistema joven como el de MCP, los gems van por detrás del spec. fast-mcp no es malo — su mantenedor es activo. El problema es que el protocolo itera más rápido que las implementaciones de terceros. Un gem que parece sólido este año puede ser "el de la spec vieja" dentro de medio año.
Si hace seis meses hubiera escrito diez mil líneas de lógica de negocio sobre fast-mcp, hoy estaría bloqueado en la spec vieja — porque el coste de reescribir sería tan alto que no me daría la gana.
Pero lo que pasó es: la capa fast-mcp siempre fue un wrapper fino. La lógica de negocio vivía en las subclases de ApplicationTool. Así que el coste real de reescribir fue:
Lo caro es la lógica de negocio. Los wrappers de protocolo deberían ser baratos y reescribibles. Ese es el favor más grande que me hizo Claude Code hace seis días — no metió la lógica de negocio en los callbacks de fast-mcp. Construyó una abstracción ApplicationTool limpia que absorbió el SDK. Por eso seis días después solo hizo falta tocar la clase base + el schema.
Lo que me llevó de "primera integración" a "subida de spec y reescritura" en 6 días es la estabilidad de Claude como modelo subyacente — y no importa demasiado en qué shell de agent estuve.