fast-mcp bị Smithery từ chối. Claude chuyển toàn bộ MCP server sang SDK chính thức trong nửa tiếng — việc viết lại rẻ vì lớp đầu tiên đã viết mỏng.
Dự án smarts vừa tích hợp MCP cách đây sáu ngày. Tôi nhờ Claude Code dựng ba tool bằng gem fast-mcp:
get_contract_info(chain, address) — tên contract, tag adapter giao thức, đếm function/eventread_contract_state(chain, address, function_name, args?) — một lần gọi view/pure, cache 60 giâyget_uniswap_v3_pool(chain, address) — panel V3 đầy đủ: giá hai chiều, liquidity, tick, TVL theo USDCommit đó là 27ca82e feat(mcp): serve live contract data over MCP via fast-mcp, 2026-04-21 lúc 1 giờ 37 sáng. Khoảng 30 phút. Trơn tru.
Sáu ngày sau, tối nay — 2026-04-27 lúc 20:24 — tôi push một commit khác: 4df08fa feat(mcp): switch to official mcp gem with Streamable HTTP transport.
Giữa hai commit ấy đã xảy ra chuyện gì?
Tôi nộp smarts vào Smithery (thư mục MCP server) để đăng ký. Scanner của họ trả ngay 405:
POST /mcp/sse → 405 Method Not Allowed
Mất một lúc tôi mới hiểu: fast-mcp 1.6.0 (latest release) vẫn chỉ triển khai HTTP+SSE transport của MCP spec 2024-11-05. Trong khi MCP client hiện đại đã chuyển sang Streamable HTTP của spec 2025-03-26 — endpoint duy nhất, POST + DELETE trên cùng một URL, không có path /sse riêng.
Nói cách khác: spec đã đi trước một bước, còn gem chủ lực của cộng đồng thì chưa theo kịp.
Tôi không định đợi fast-mcp. Lớp MCP server tồn tại để client tiêu thụ — tương thích spec quan trọng hơn chuyện gem nào viết thoải mái hơn.
Tôi đổi fast-mcp sang gem chính thức của Anthropic là mcp 0.14.0. Vòng này tôi mở một thread mới trong Amp (bên dưới vẫn là Claude Opus, cùng model với Claude Code).
Những gì hạ cánh trong đợt migrate:
Transport thay nội thất
# Cũ: fast-mcp tự mount lên /mcp kèm /sse
# Mới: mount thủ công, endpoint duy nhất
mount StreamableHTTPTransport.new(server, stateless: true), at: "/mcp"
stateless: true là điểm cốt lõi. Server không giữ trạng thái per-session trong memory, nhờ vậy Puma có thể chạy với workers > 0 và scale ngang mà không cần sticky session.
Tái cấu trúc Tool API
Lối viết cũ của fast-mcp:
class GetContractInfoTool < ApplicationTool
arguments do
required(:chain).filled(:string)
required(:address).filled(:string)
end
def call(chain:, address:)
# logic nghiệp vụ
end
end
Gem chính thức đẩy phần đường ống của SDK xuống lớp cơ sở:
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:)
# logic nghiệp vụ, trả về Hash
end
end
Subclass chỉ quan tâm payload(**args) trả về Hash gì; lớp cơ sở bọc nó thành text block JSON-encoded bên trong MCP::Tool::Response. Hai cái lợi:
Tool.payload(chain:, address:), khỏi bóc envelope giao thứcDSL của dry-schema tôi đổi sang JSON Schema literal thuần. Trông như đi thụt lùi, nhưng input_schema đúng là cái mà client đọc trực tiếp từ MCP spec — viết ở dạng nguyên thủy của spec ngay thẳng hơn.
Test chuyển nhà
433 test, đổi từ Tool.new.call(...) sang Tool.payload(...). Tất cả xanh.
Đồng bộ discovery / docs
.well-known/mcp.json với transport: "streamable-http", protocol_version: "2025-03-26"https://smarts.md/mcpclaude mcp add đổi từ --transport sse sang --transport httpmcp là primary, fast-mcp là legacy/deprecatedLần tích hợp MCP đầu mất 30 phút. Lần viết lại cũng tầm đó. Cách nhau sáu ngày.
Cái tôi nhận ra: trong một hệ sinh thái non như MCP, gem đi sau spec là chuyện bình thường. fast-mcp không tệ — maintainer chăm. Vấn đề là chính giao thức tiến hóa nhanh hơn các implement của bên thứ ba. Một gem trông vững năm nay, nửa năm sau có thể trở thành "cái dùng spec cũ".
Nếu sáu tháng trước tôi viết mười ngàn dòng business logic trên fast-mcp, hôm nay tôi gần như chắc chắn bị khóa vào spec cũ — vì cái giá phải trả để viết lại sẽ cao đến mức tôi không nhấc nổi mình lên làm.
Nhưng thực tế là: lớp fast-mcp ngay từ đầu chỉ là thin wrapper. Business logic sống trong subclass của ApplicationTool. Nên cái giá thực của việc viết lại là:
Cái đắt là business logic. Wrapper giao thức nên rẻ và viết lại được. Đó là ơn lớn nhất Claude Code đã làm cho tôi sáu ngày trước — nó không nhét business logic vào callback fast-mcp. Nó dựng một lớp trừu tượng ApplicationTool sạch sẽ hấp thụ phần SDK. Bởi vậy sáu ngày sau khi đổi SDK, chỉ cần đụng lớp cơ sở + schema.
Cái đưa tôi đi từ "tích hợp lần đầu" tới "nâng cấp spec và viết lại" trong 6 ngày là sự ổn định của Claude với tư cách model nền — tôi ngồi trong agent shell nào, không quá quan trọng.
Mã nguồn: https://github.com/defi-io/smarts