mcp-diff — Ruby port (in progress)
Parallel port of mcp-diff to Ruby, on the ruby-port branch. The TypeScript
packages under ../packages stay the shippable version until this reaches
feature parity (24 rules + CLI + Action + green tests).
Decisions driving this port
- Language: Ruby (user decision, supersedes the TS default for this branch).
- MCP SDK: the official
mcpgem (modelcontextprotocol/ruby-sdk) — honors the project's "anchor only to the official SDK" rule. Its client subsystem (MCP::Client+MCP::Client::Stdio/http) covers the full capture surface: connect/InitializeResult (capabilities, protocolVersion, serverInfo, instructions),#tools/#resources/#resource_templates/#prompts, all auto-paginated. - Distribution: RubyGems (
gem install) + a Ruby-based GitHub Action — replaces the TSnpxflow. (Verify the gem namemcp_diff/mcp-diffon RubyGems, mirroring the npm-naming saga.)
Module boundaries (mirror the TS split; keep core pure)
McpDiff::Core— normalize → diff → classify + rule catalog. Zero I/O.McpDiff::Client— capture over the officialmcpgem. The only server I/O.McpDiff::Cli— init/snapshot/check/update/diff/explain; exit codes 0/1/2.
Spike result (verified — Ruby 3.3.12, mcp 1.0.0)
Capture works and is faithful. Gotcha found & handled: the gem's public
#tools/#resources/… wrappers deserialize into objects that DROP annotations
and title — both part of mcp-diff's contract (BEHAVIOR class). The capture
layer reads the raw JSON one layer down via MCP::Client's own private
request (full JSON-RPC envelope + id correlation), so nothing is lost.
capture_test.rb locks this in (asserts add_doc's annotations survive).
Done on this branch (green — 35 tests / 94 assertions)
McpDiff::Core::Canonical+Normalize— deterministic snapshot + contractHash.McpDiff::Client.capture— capability-gated, cursor-paginated capture over the official gem (stdio verified against two real servers; HTTP wired viaMCP::Client::HTTP, not yet exercised by a test).Core::Diff+Classify+SchemaDiff(D9) + the full 24-rule catalog — verified against the SAME fixtures the TS suite uses (test/fixtures/).Cli— init/snapshot/check/update/diff/explain/rules, exit codes 0/1/2, pretty/JSON/MD. Output is byte-identical to the TS CLI (verified on the demo).exe/mcp-diff, gemspec builds clean (mcp_diff-0.1.0.gem).
Done since (green — full suite: 45 tests / 157 assertions)
test/schema_diff_test.rb— the D9 variance primitives (port of the TS units).test/catalog_test.rb— D8 no-drift: the catalog vs context/RULES.md.action/action.yml— Ruby-native composite Action (setup-ruby →gem install mcp_diff→mcp-diff --md check→ PR comment + gate)..github/workflows/ruby-ci.yml— runs the Ruby suite on CI.
Release artifact validated
Built + installed the gem and ran the installed mcp-diff binary end-to-end:
init (live capture) wrote config + lockfile, check returned "✓ contract
unchanged" (exit 0). So gem install mcp_diff yields a working CLI.
Standalone
test/fixtures/server.rb is a hand-rolled MCP stdio server mirroring the TS
fixture — the whole suite runs with only Ruby (no Node), and ruby-ci.yml
reflects that.
Remaining before a Ruby release
- Publish to RubyGems — the name
mcp_diff(andmcp-diff) is FREE on RubyGems (verified), so no npm-style workaround. The release is wired:.github/workflows/ruby-release.ymlbuilds +gem pushon av*tag. It needs the repo secretRUBYGEMS_API_KEY(your RubyGems API key — the analogue of NPM_TOKEN). To ship: add the secret, thengit tag v0.1.0 && git push --tags. - Nice-to-have: split rules into one-file-per-rule (contributor funnel); an HTTP-transport capture test.
Port stream tracking
Mirrors ../context/STREAMS.md S1–S5. Formal D-decisions land in context/
once the spike validates the foundation (not before — the rewrite isn't final
until capture is proven in Ruby).