Smithery відмовив fast-mcp. Claude за пів години переніс увесь MCP server на офіційний SDK — переписування коштувало дешево, бо перший шар був тонкий.
У smarts MCP підключили шість днів тому. Я попросив Claude Code підняти три tool через gem fast-mcp:
get_contract_info(chain, address) — ім'я контракту, тег адаптера протоколу, лічильники function/eventread_contract_state(chain, address, function_name, args?) — одиничний view/pure-виклик, кеш 60sget_uniswap_v3_pool(chain, address) — повна панель V3: ціна в обидва боки, liquidity, tick, USD-TVLТой commit — 27ca82e feat(mcp): serve live contract data over MCP via fast-mcp, 2026-04-21 о 1:37. Близько 30 хвилин. Гладко.
Шість днів по тому, сьогодні ввечері — 2026-04-27 о 20:24 — я запушив інший commit: 4df08fa feat(mcp): switch to official mcp gem with Streamable HTTP transport.
Що сталося між ними?
Я надіслав smarts на реєстрацію в Smithery (директорію MCP-серверів). Їхній сканер одразу повернув 405:
POST /mcp/sse → 405 Method Not Allowed
Знадобився час, щоб розібратись: fast-mcp 1.6.0 (останній release) досі реалізує HTTP+SSE-transport специфікації MCP 2024-11-05. А сучасні MCP-клієнти вже перейшли на Streamable HTTP зі специфікації 2025-03-26 — єдиний endpoint, POST + DELETE на тій самій URL, ніякого окремого /sse.
Інакше кажучи: специфікація крокнула вперед, а домінантний community gem не наздогнав.
Чекати fast-mcp я не збирався. Шар MCP-сервера існує для того, щоб його споживали клієнти — відповідність специфікації важить більше, ніж те, який gem зручніше писати.
Замінив fast-mcp на офіційний gem mcp 0.14.0 від Anthropic. На цей раунд я відкрив новий thread в Amp (під капотом усе той же Claude Opus, що й у Claude Code).
Що приземлилося під час міграції:
Transport перекладено
# Старий: fast-mcp монтується автоматично на /mcp з /sse
# Новий: явний mount, єдиний endpoint
mount StreamableHTTPTransport.new(server, stateless: true), at: "/mcp"
Ключ — stateless: true. Сервер не тримає per-session стану в пам'яті, тож Puma може працювати з workers > 0 і масштабуватись горизонтально без sticky session.
Реструктуризація Tool API
Старий стиль fast-mcp:
class GetContractInfoTool < ApplicationTool
arguments do
required(:chain).filled(:string)
required(:address).filled(:string)
end
def call(chain:, address:)
# бізнес-логіка
end
end
Офіційний gem заганяє SDK-обв'язку в базовий клас:
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:)
# бізнес-логіка, повертає Hash
end
end
Підкласи переймаються лише тим, який Hash повертає payload(**args); базовий клас загортає його в JSON-encoded text block всередині MCP::Tool::Response. Два плюси:
Tool.payload(chain:, address:), без розпакування protocol envelopeDSL dry-schema замінив на чисті літерали JSON Schema. Зовні це виглядає як крок назад, але input_schema — рівно те, що клієнт читає з MCP-специфікації напряму, тож писати у вигляді примітиву специфікації чесніше.
Переїзд тестів
433 тести, перемкнуто з Tool.new.call(...) на Tool.payload(...). Всі зелені.
Синхронізація discovery / docs
.well-known/mcp.json з transport: "streamable-http", protocol_version: "2025-03-26"https://smarts.md/mcpclaude mcp add змінився з --transport sse на --transport httpmcp як primary, fast-mcp — як legacy/deprecatedПерша інтеграція MCP зайняла 30 хвилин. Переписування — приблизно стільки ж. Між ними — шість днів.
Що до мене дійшло: у молодій екосистемі на кшталт MCP gems відстають від специфікації — це норма. fast-mcp не поганий — мейнтейнер активний. Проблема в тому, що сам протокол ітерує швидше за сторонні реалізації. Gem, що цього року виглядає надійно, за пів року може стати «тим, що на старій спеці».
Якби півроку тому я написав десять тисяч рядків бізнес-логіки на fast-mcp, сьогодні був би прикутий до старої спеки — бо ціна переписування була б такою, що я б не зважився її платити.
Але насправді вийшло так: шар fast-mcp з самого початку був тонкою обгорткою. Бізнес-логіка жила в підкласах ApplicationTool. Тому реальна ціна переписування вийшла така:
Дорога частина — це бізнес-логіка. Обгортки протоколу мають бути дешевими і переписуваними. Найбільша послуга, яку Claude Code зробив мені шість днів тому, у цьому — він не втрамбував бізнес-логіку в callbacks fast-mcp. Він збудував чисту абстракцію ApplicationTool, що поглинає SDK. Тому шість днів по тому заміна SDK обмежилась базовим класом і схемою.
Те, що дозволило мені за 6 днів пройти шлях «перша інтеграція → апгрейд спеки → переписування», — це стабільність Claude як базової моделі. В якому саме agent-shell я сидів, важить мало.