Free

Demander à Claude de réécrire le MCP que j'avais monté il y a 6 jours

Smithery a rejeté fast-mcp. Claude a déplacé tout le serveur MCP vers le SDK officiel en une demi-heure — la réécriture a coûté peu parce que la première version était fine.


Le projet smarts a intégré MCP il y a six jours. J'avais demandé à Claude Code de mettre en place trois tools avec le gem fast-mcp :

  • get_contract_info(chain, address) — nom du contract, tag de l'adapter de protocole, comptes de function / event
  • read_contract_state(chain, address, function_name, args?) — un seul appel view / pure, cache de 60s
  • get_uniswap_v3_pool(chain, address) — panneau V3 complet : prix bidirectionnel, liquidity, tick, TVL en USD

Ce commit-là, c'était 27ca82e feat(mcp): serve live contract data over MCP via fast-mcp, le 2026-04-21 à 1h37. Une trentaine de minutes. Tout roulant.

Six jours plus tard, ce soir — 2026-04-27 à 20h24 — j'ai poussé un autre commit : 4df08fa feat(mcp): switch to official mcp gem with Streamable HTTP transport.

Qu'est-ce qui s'est passé entre les deux ?

fast-mcp s'est fait jeter par Smithery

J'ai soumis smarts à Smithery (un annuaire de MCP servers) pour l'enregistrer. Leur scanner m'a balancé 405 direct :

POST /mcp/sse → 405 Method Not Allowed

J'ai mis un peu de temps à comprendre : fast-mcp 1.6.0 (la dernière release) implémente toujours le transport HTTP+SSE de la MCP spec 2024-11-05. Les clients MCP modernes ont déjà basculé sur le Streamable HTTP de la spec 2025-03-26 — endpoint unique, POST + DELETE sur la même URL, pas de chemin /sse séparé.

Autrement dit : la spec a avancé, et le gem dominant de la communauté n'a pas suivi.

Pas question d'attendre fast-mcp. La couche serveur MCP existe pour être consommée par les clients — la compatibilité de spec pèse plus que le gem qui vous plaît à écrire.

Changer la fondation, ~30 minutes

J'ai remplacé fast-mcp par le gem officiel mcp 0.14.0 d'Anthropic. Pour cette manche, j'ai ouvert un nouveau thread dans Amp (avec Claude Opus en dessous, le même que Claude Code).

Ce qui a atterri pendant la migration :

Transport, recâblé

# Ancien : fast-mcp monte automatiquement sur /mcp avec /sse
# Nouveau : mount explicite, endpoint unique
mount StreamableHTTPTransport.new(server, stateless: true), at: "/mcp"

stateless: true est la clé. Le serveur ne garde plus d'état per-session en mémoire, ce qui permet à Puma de tourner avec workers > 0 et de scaler horizontalement sans sticky session.

Restructuration de la Tool API

L'ancienne forme fast-mcp :

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

  def call(chain:, address:)
    # logique métier
  end
end

Le gem officiel pousse la plomberie SDK dans la classe de 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:)
    # logique métier, renvoie un Hash
  end
end

Les sous-classes ne s'occupent que du Hash que renvoie payload(**args) ; la classe de base l'enveloppe dans un text block JSON-encoded au sein de MCP::Tool::Response. Deux gains :

  1. La plomberie SDK arrête de s'éparpiller dans chaque classe tool
  2. Les tests peuvent assert directement sur le Hash de Tool.payload(chain:, address:), sans dépiauter l'enveloppe de protocole

Le DSL dry-schema a été remplacé par des littéraux JSON Schema bruts. À première vue ça ressemble à un retour en arrière, mais input_schema est ce que les clients lisent littéralement dans la MCP spec — écrire à la primitive de la spec est plus honnête.

Tests, déménagement

433 tests, basculés de Tool.new.call(...) à Tool.payload(...). Tous au vert.

Synchronisation discovery / docs

  • .well-known/mcp.json avec transport: "streamable-http", protocol_version: "2025-03-26"
  • README, llms.txt, smithery.yaml — tous mis à jour vers https://smarts.md/mcp
  • Flag claude mcp add passé de --transport sse à --transport http
  • Dans CLAUDE.md, la section tech stack liste maintenant mcp comme primary et fast-mcp comme legacy/deprecated

« Avoir le cran de réécrire » est le vrai levier

La première intégration MCP m'a pris 30 minutes. La réécriture, à peu près pareil. Six jours d'écart.

Ce qui m'a frappé : dans un écosystème jeune comme MCP, les gems sont en retard sur la spec. fast-mcp n'est pas mauvais — son mainteneur est actif. Le problème, c'est que le protocole itère plus vite que les implémentations tierces. Un gem qui paraît solide cette année peut être « celui de l'ancienne spec » dans six mois.

Si j'avais écrit dix mille lignes de logique métier sur fast-mcp il y a six mois, aujourd'hui je serais cloué à l'ancienne spec — parce que le coût de réécriture serait trop élevé pour que je m'y attelle.

Mais voilà ce qui s'est passé : la couche fast-mcp a toujours été un thin wrapper. La logique métier vivait dans les sous-classes de ApplicationTool. Du coup le coût réel de la réécriture a été :

  • Changer la classe de base (une fois)
  • Réécrire le DSL du schema dans chacune des 4 classes tool (~10 lignes chacune)
  • Changer le style d'invocation dans les tests (mécanique)
  • Changer une ligne de mount dans routes
  • Search-and-replace de l'URL des docs

Le cher, c'est la logique métier. Les wrappers de protocole devraient être bon marché et réécrissables. C'est le plus grand service que Claude Code m'a rendu il y a six jours — il n'a pas fourré la logique métier dans les callbacks fast-mcp. Il a construit une abstraction ApplicationTool propre qui absorbait le SDK. Du coup, six jours plus tard, l'échange de SDK n'a demandé que la classe de base + le schema.

Ce qui m'a permis d'aller de « première intégration » à « upgrade de spec et réécriture » en 6 jours, c'est la stabilité de Claude comme modèle de fond — peu importe dans quel shell d'agent j'étais.

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