Free

Chiedere a Claude di riscrivere l'MCP che ho montato 6 giorni fa

Smithery ha respinto fast-mcp. Claude ha spostato l'intero server MCP sul SDK ufficiale in mezz'ora — la riscrittura è costata poco perché il primo strato era sottile.


Il progetto smarts ha integrato MCP sei giorni fa. Avevo chiesto a Claude Code di tirare su tre tool col gem fast-mcp:

  • get_contract_info(chain, address) — nome del contract, tag dell'adapter di protocollo, conteggi di function/event
  • read_contract_state(chain, address, function_name, args?) — singola chiamata view/pure, cache da 60s
  • get_uniswap_v3_pool(chain, address) — pannello V3 completo: prezzo bidirezionale, liquidity, tick, TVL in USD

Quel commit era 27ca82e feat(mcp): serve live contract data over MCP via fast-mcp, il 2026-04-21 all'1:37. Una trentina di minuti. Filato.

Sei giorni dopo, stasera — 2026-04-27 alle 20:24 — ho pushato un altro commit: 4df08fa feat(mcp): switch to official mcp gem with Streamable HTTP transport.

Cosa è successo in mezzo?

Smithery ha respinto fast-mcp

Ho mandato smarts su Smithery (una directory per server MCP) per registrarlo. Il loro scanner mi ha sparato un 405 subito:

POST /mcp/sse → 405 Method Not Allowed

Ho impiegato un attimo a capire: fast-mcp 1.6.0 (latest release) implementa ancora l'HTTP+SSE transport della MCP spec 2024-11-05. I client MCP moderni si sono già spostati allo Streamable HTTP della spec 2025-03-26 — endpoint unico, POST + DELETE sulla stessa URL, niente percorso /sse separato.

Tradotto: la spec è andata avanti, e il gem dominante della community non l'ha ancora seguita.

Non avevo intenzione di aspettare fast-mcp. Il layer del server MCP esiste perché i client lo consumino — la compatibilità con la spec pesa più di quale gem ti piaccia di più scrivere.

Cambiare le fondamenta, ~30 minuti

Ho sostituito fast-mcp con il gem ufficiale mcp 0.14.0 di Anthropic. Per questo giro ho aperto un nuovo thread su Amp (sotto gira sempre Claude Opus, lo stesso di Claude Code).

Cosa ha portato la migrazione:

Transport, nuovo cablaggio

# Vecchio: fast-mcp monta automaticamente su /mcp con /sse
# Nuovo: mount esplicito, endpoint unico
mount StreamableHTTPTransport.new(server, stateless: true), at: "/mcp"

stateless: true è il punto. Il server non tiene stato per-sessione in memoria, e così Puma può girare con workers > 0 e scalare orizzontalmente senza sticky session.

Ristrutturazione della Tool API

La forma vecchia di fast-mcp:

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

  def call(chain:, address:)
    # logica di business
  end
end

Il gem ufficiale spinge l'idraulica del SDK nella classe 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:)
    # logica di business, ritorna un Hash
  end
end

Le sottoclassi pensano solo a quale Hash ritorni payload(**args); la classe base lo avvolge in un text block JSON-encoded dentro MCP::Tool::Response. Due vantaggi:

  1. L'idraulica del SDK smette di sparpagliarsi in ogni classe tool
  2. I test possono asserire direttamente sull'Hash di Tool.payload(chain:, address:), senza scartare l'envelope di protocollo

Il DSL di dry-schema l'ho cambiato in literal raw JSON Schema. Sembra un passo indietro, ma input_schema è ciò che il client legge letteralmente dalla MCP spec — scriverlo come primitiva della spec è più onesto.

Trasloco dei test

433 test, passati da Tool.new.call(...) a Tool.payload(...). Tutti verdi.

Sincronizzazione discovery / docs

  • .well-known/mcp.json con transport: "streamable-http", protocol_version: "2025-03-26"
  • README, llms.txt, smithery.yaml — tutti aggiornati a https://smarts.md/mcp
  • Flag di claude mcp add cambiato da --transport sse a --transport http
  • Nel CLAUDE.md, la sezione tech stack ora elenca mcp come primary e fast-mcp come legacy/deprecated

"Pronto a riscrivere" è la leva vera

La prima integrazione MCP è costata 30 minuti. La riscrittura più o meno lo stesso. Sei giorni di distanza.

Quello che ho realizzato: in un ecosistema giovane come MCP, i gem sono indietro rispetto alla spec. fast-mcp non è male — il maintainer è attivo. Il problema è che il protocollo itera più veloce delle implementazioni di terzi. Un gem che quest'anno sembra solido può diventare "quello della vecchia spec" tra sei mesi.

Se sei mesi fa avessi scritto diecimila righe di logica di business sopra fast-mcp, oggi sarei probabilmente bloccato sulla vecchia spec — perché il costo della riscrittura sarebbe troppo alto per decidermi a pagarlo.

Ma in realtà è andata così: il layer fast-mcp è sempre stato un thin wrapper. La logica di business viveva nelle sottoclassi di ApplicationTool. Il costo reale della riscrittura quindi è stato:

  • Cambiare la classe base (una volta)
  • Riscrivere il DSL del schema in ognuna delle 4 classi tool (~10 righe l'una)
  • Cambiare lo stile di invocazione dei test (meccanico)
  • Cambiare una riga di mount in routes
  • Search-and-replace dell'URL nei docs

Il costoso è la logica di business. I wrapper di protocollo dovrebbero essere economici e riscrivibili. È il favore più grosso che Claude Code mi ha fatto sei giorni fa — non ha infilato la logica di business nei callback di fast-mcp. Ha costruito un'astrazione ApplicationTool pulita che assorbe il SDK. Per questo sei giorni dopo lo scambio di SDK ha richiesto solo classe base + schema.

Quello che mi ha portato da "prima integrazione" a "upgrade di spec e riscrittura" in 6 giorni è la stabilità di Claude come modello di fondo — in quale shell di agent fossi conta poco.

Sorgente: https://github.com/defi-io/smarts