Module: McpDiff::Core::Diff

Defined in:
lib/mcp_diff/core/diff.rb

Overview

Structural diff between two snapshots. Emits typed Change hashes (symbol keys); no severities. Port of core/src/diff.ts. Entity payloads (before/ after) keep their string-keyed entry shape.

Class Method Summary collapse

Class Method Details

.capability_changes(before_caps, after_caps) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/mcp_diff/core/diff.rb', line 49

def capability_changes(before_caps, after_caps)
  changes = []
  (before_caps.keys | after_caps.keys).sort.each do |k|
    b_present = before_caps.key?(k)
    a_present = after_caps.key?(k)
    if !b_present && a_present
      changes << { kind: "capability-added", capability: k, after: after_caps[k] }
    elsif b_present && !a_present
      changes << { kind: "capability-removed", capability: k, before: before_caps[k] }
    elsif b_present && a_present && !Canonical.deep_equal(before_caps[k], after_caps[k])
      changes << { kind: "capability-modified", capability: k, before: before_caps[k], after: after_caps[k] }
    end
  end
  changes
end

.diff(before, after) ⇒ Object



13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/mcp_diff/core/diff.rb', line 13

def diff(before, after)
  changes = []
  changes.concat(entity_changes(before["tools"], after["tools"], "name",
                                { added: "tool-added", removed: "tool-removed", modified: "tool-modified" }, :name))
  changes.concat(entity_changes(before["resources"], after["resources"], "uri",
                                { added: "resource-added", removed: "resource-removed", modified: "resource-modified" }, :uri))
  changes.concat(entity_changes(before["resourceTemplates"], after["resourceTemplates"], "uriTemplate",
                                { added: "resource-template-added", removed: "resource-template-removed", modified: "resource-template-modified" }, :uriTemplate))
  changes.concat(entity_changes(before["prompts"], after["prompts"], "name",
                                { added: "prompt-added", removed: "prompt-removed", modified: "prompt-modified" }, :name))
  changes.concat(capability_changes(before["capabilities"] || {}, after["capabilities"] || {}))
  changes.concat(scalar_changes(before, after))
  changes
end

.entity_changes(before, after, key, kinds, key_field) ⇒ Object



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/mcp_diff/core/diff.rb', line 28

def entity_changes(before, after, key, kinds, key_field)
  before ||= []
  after ||= []
  before_map = before.to_h { |item| [item[key], item] }
  after_map = after.to_h { |item| [item[key], item] }
  changes = []

  before_map.each do |k, item|
    nxt = after_map[k]
    if nxt.nil? && !after_map.key?(k)
      changes << { kind: kinds[:removed], key_field => k, before: item }
    elsif !Canonical.deep_equal(item, nxt)
      changes << { kind: kinds[:modified], key_field => k, before: item, after: nxt }
    end
  end
  after_map.each do |k, item|
    changes << { kind: kinds[:added], key_field => k, after: item } unless before_map.key?(k)
  end
  changes
end

.scalar_changes(before, after) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/mcp_diff/core/diff.rb', line 65

def scalar_changes(before, after)
  changes = []
  unless before["instructions"] == after["instructions"]
    changes << { kind: "instructions-changed", before: before["instructions"], after: after["instructions"] }
  end
  unless Canonical.deep_equal(before["server"], after["server"])
    changes << { kind: "server-info-changed", before: before["server"], after: after["server"] }
  end
  unless before["protocolVersion"] == after["protocolVersion"]
    changes << { kind: "protocol-version-changed", before: before["protocolVersion"], after: after["protocolVersion"] }
  end
  changes
end