Free

Прошу Claude переписать MCP, который собрал шесть дней назад

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


В 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