Free

Proszę Claude'a, żeby przepisał MCP, który postawiłem 6 dni temu

Smithery odrzuciło fast-mcp. Claude w pół godziny przeniósł cały serwer MCP na oficjalny SDK — przepisanie wyszło tanio, bo pierwsza warstwa była cienka.


W projekcie smarts podpięliśmy MCP sześć dni temu. Poprosiłem Claude'a Code, żeby na gemie fast-mcp postawił trzy tooly:

  • get_contract_info(chain, address) — nazwa kontraktu, tag adaptera protokołu, liczniki function/event
  • read_contract_state(chain, address, function_name, args?) — pojedyncze wywołanie view/pure, cache 60s
  • get_uniswap_v3_pool(chain, address) — pełny panel V3: cena w obu kierunkach, liquidity, tick, USD TVL

Tamten commit to 27ca82e feat(mcp): serve live contract data over MCP via fast-mcp, 2026-04-21 o 1:37. Jakieś 30 minut. Gładko.

Sześć dni później, dziś wieczorem — 2026-04-27 o 20:24 — wypchnąłem kolejny commit: 4df08fa feat(mcp): switch to official mcp gem with Streamable HTTP transport.

Co się zdarzyło pomiędzy?

Smithery odrzuciło fast-mcp

Zgłosiłem smarts do Smithery (katalogu serwerów MCP) do rejestracji. Ich skaner od ręki rzucił 405:

POST /mcp/sse → 405 Method Not Allowed

Chwilę zajęło mi rozkminienie: fast-mcp 1.6.0 (najnowszy release) wciąż implementuje HTTP+SSE transport ze specyfikacji MCP 2024-11-05. Tymczasem nowoczesne klienty MCP przeszły już na Streamable HTTP ze specyfikacji 2025-03-26 — jeden endpoint, POST + DELETE na tym samym URL-u, bez osobnej ścieżki /sse.

Innymi słowy: specyfikacja zrobiła krok do przodu, a dominujący gem w społeczności jeszcze nie dogonił.

Nie zamierzałem czekać na fast-mcp. Warstwa serwera MCP istnieje po to, żeby konsumowały ją klienty — zgodność ze specyfikacją waży więcej niż to, który gem najlepiej leży w ręce.

Wymiana fundamentu, ~30 minut

Zamieniłem fast-mcp na oficjalny gem mcp 0.14.0 od Anthropic. W tej rundzie otworzyłem nowy thread w Amp (pod spodem dalej kręci się Claude Opus, ten sam co w Claude Code).

Co wylądowało w migracji:

Transport, przepięcie

# Stary: fast-mcp montuje się automatycznie na /mcp z /sse
# Nowy: jawny mount, pojedynczy endpoint
mount StreamableHTTPTransport.new(server, stateless: true), at: "/mcp"

stateless: true to sedno. Serwer nie trzyma stanu per-session w pamięci, więc Puma może chodzić z workers > 0 i skalować się horyzontalnie bez sticky session.

Restrukturyzacja Tool API

Stara forma fast-mcp:

class GetContractInfoTool < ApplicationTool
  arguments do
    required(:chain).filled(:string)
    required(:address).filled(:string)
  end

  def call(chain:, address:)
    # logika biznesowa
  end
end

Oficjalny gem przepycha hydraulikę SDK do klasy bazowej:

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:)
    # logika biznesowa, zwraca Hash
  end
end

Subklasy zajmują się tylko tym, jaki Hash zwraca payload(**args); klasa bazowa opakowuje to w JSON-encoded text block wewnątrz MCP::Tool::Response. Dwa zyski:

  1. Hydraulika SDK przestaje rozłazić się po każdej klasie toolowej
  2. Testy mogą asercjonować bezpośrednio na Hashu z Tool.payload(chain:, address:), bez rozpakowywania protokolarnego envelope

DSL z dry-schema podmieniłem na surowe literały JSON Schema. Z zewnątrz wygląda to jak krok wstecz, ale input_schema to dokładnie to, co klient czyta wprost ze specyfikacji MCP — pisanie w postaci prymitywu specyfikacji jest uczciwsze.

Przeprowadzka testów

433 testy, przełączone z Tool.new.call(...) na Tool.payload(...). Wszystkie zielone.

Synchronizacja discovery / docs

  • .well-known/mcp.json z transport: "streamable-http", protocol_version: "2025-03-26"
  • README, llms.txt, smithery.yaml — wszystkie zaktualizowane na https://smarts.md/mcp
  • Flaga claude mcp add zmieniona z --transport sse na --transport http
  • W CLAUDE.md sekcja tech stack oznacza teraz mcp jako primary, a fast-mcp jako legacy/deprecated

„Gotowość do przepisania" jest prawdziwą dźwignią

Pierwsza integracja MCP zajęła 30 minut. Przepisanie — mniej więcej tyle samo. Między nimi — sześć dni.

Co mi zaświtało: w młodym ekosystemie jak MCP gemy wleczą się za specyfikacją — to norma. fast-mcp nie jest zły — maintainer jest aktywny. Problem w tym, że sam protokół iteruje szybciej niż implementacje od trzecich stron. Gem, który w tym roku wygląda solidnie, za pół roku może być „tym ze starej speki".

Gdybym pół roku temu napisał na fast-mcp dziesięć tysięcy linii logiki biznesowej, dziś byłbym przykuty do starej speki — bo koszt przepisania byłby tak duży, że nie zebrałbym się do tego.

Ale w rzeczywistości było tak: warstwa fast-mcp od początku była cienkim wrapperem. Logika biznesowa siedziała w subklasach ApplicationTool. Realny koszt przepisania to więc:

  • Zmienić klasę bazową (raz)
  • Przepisać DSL schematu w każdej z 4 klas toolowych (~10 linii na klasę)
  • Zmienić styl wywołania w testach (mechanicznie)
  • Zmienić jedną linijkę mountu w routes
  • Search-and-replace URL w docsach

Drogi jest biznes. Wrappery protokołu powinny być tanie i przepisywalne. To największa przysługa, jaką sześć dni temu wyświadczył mi Claude Code — nie wpakował logiki biznesowej w callbacki fast-mcp. Zbudował czystą abstrakcję ApplicationTool, która wchłania SDK. Dlatego sześć dni później wymiana SDK wymagała tylko klasy bazowej i schematu.

To, co pozwoliło mi w 6 dni przejść drogę „pierwsza integracja → upgrade speki → przepisanie", to stabilność Claude'a jako modelu pod spodem. W którym agentowym shellu wtedy siedziałem — sprawa drugorzędna.

Źródła: https://github.com/defi-io/smarts