Free

Прошу Claude переписати MCP, який зібрав шість днів тому

Smithery відмовив fast-mcp. Claude за пів години переніс увесь MCP server на офіційний SDK — переписування коштувало дешево, бо перший шар був тонкий.


У smarts MCP підключили шість днів тому. Я попросив Claude Code підняти три tool через gem fast-mcp:

  • get_contract_info(chain, address) — ім'я контракту, тег адаптера протоколу, лічильники function/event
  • read_contract_state(chain, address, function_name, args?) — одиничний view/pure-виклик, кеш 60s
  • get_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.

Що сталося між ними?

Smithery відмовив fast-mcp

Я надіслав 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 зручніше писати.

Міняємо фундамент, ~30 хвилин

Замінив 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. Два плюси:

  1. SDK-обв'язка перестає розповзатись по кожному tool-класу
  2. Тести можуть стверджувати прямо на Hash з Tool.payload(chain:, address:), без розпакування protocol envelope

DSL 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"
  • README, llms.txt, smithery.yaml — усі оновлено на https://smarts.md/mcp
  • Прапорець claude mcp add змінився з --transport sse на --transport http
  • У CLAUDE.md секція tech stack тепер позначає mcp як primary, fast-mcp — як legacy/deprecated

«Готовність переписати» — це і є важіль

Перша інтеграція MCP зайняла 30 хвилин. Переписування — приблизно стільки ж. Між ними — шість днів.

Що до мене дійшло: у молодій екосистемі на кшталт MCP gems відстають від специфікації — це норма. fast-mcp не поганий — мейнтейнер активний. Проблема в тому, що сам протокол ітерує швидше за сторонні реалізації. Gem, що цього року виглядає надійно, за пів року може стати «тим, що на старій спеці».

Якби півроку тому я написав десять тисяч рядків бізнес-логіки на fast-mcp, сьогодні був би прикутий до старої спеки — бо ціна переписування була б такою, що я б не зважився її платити.

Але насправді вийшло так: шар fast-mcp з самого початку був тонкою обгорткою. Бізнес-логіка жила в підкласах ApplicationTool. Тому реальна ціна переписування вийшла така:

  • Поміняти базовий клас (один раз)
  • Переписати DSL схеми в кожному з 4 tool-класів (~10 рядків на клас)
  • Поміняти стиль виклику тестів (механічно)
  • Поміняти один рядок mount в routes
  • Search-and-replace по URL у docs

Дорога частина — це бізнес-логіка. Обгортки протоколу мають бути дешевими і переписуваними. Найбільша послуга, яку Claude Code зробив мені шість днів тому, у цьому — він не втрамбував бізнес-логіку в callbacks fast-mcp. Він збудував чисту абстракцію ApplicationTool, що поглинає SDK. Тому шість днів по тому заміна SDK обмежилась базовим класом і схемою.

Те, що дозволило мені за 6 днів пройти шлях «перша інтеграція → апгрейд спеки → переписування», — це стабільність Claude як базової моделі. В якому саме agent-shell я сидів, важить мало.

Сирці: https://github.com/defi-io/smarts