Module: McpDiff::Config
- Defined in:
- lib/mcp_diff/config.rb
Overview
Config loading + validation (port of cli/src/config.ts). Actionable error messages: every failure says how to fix it. Config uses string keys (JSON).
Constant Summary collapse
- DEFAULT_CONFIG_PATH =
"mcp-diff.config.json"- DEFAULT_LOCKFILE =
"mcp.lock.json"
Class Method Summary collapse
- .apply_profile(config, profile) ⇒ Object
- .detect_mcp_json ⇒ Object
- .load_config(path, profile = nil) ⇒ Object
- .read_config_text(path) ⇒ Object
-
.shell_split(command) ⇒ Object
Split a command line into argv, honoring single/double quotes.
- .validate_config(path, config) ⇒ Object
- .validate_env(value, invalid) ⇒ Object
- .validate_ignore(value, invalid) ⇒ Object
- .validate_server(server, invalid) ⇒ Object
- .validate_timeout(value, invalid) ⇒ Object
Class Method Details
.apply_profile(config, profile) ⇒ Object
33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/mcp_diff/config.rb', line 33 def apply_profile(config, profile) = (config["profiles"] || {})[profile] if .nil? known = (config["profiles"] || {}).keys.join(", ") known = "none defined" if known.empty? raise "unknown profile \"#{profile}\" — known profiles: #{known}" end merged = config.merge() merged["env"] = (config["env"] || {}).merge(["env"] || {}) merged["server"] = ["server"] || config["server"] merged end |
.detect_mcp_json ⇒ Object
92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/mcp_diff/config.rb', line 92 def detect_mcp_json return nil unless File.exist?(".mcp.json") parsed = JSON.parse(File.read(".mcp.json")) servers = parsed["mcpServers"] || {} _name, server = servers.first return nil unless server.is_a?(Hash) && server["command"].is_a?(String) args = (server["args"] || []).map { |a| a.match?(/\s/) ? "\"#{a}\"" : a }.join(" ") { "stdio" => args.empty? ? server["command"] : "#{server['command']} #{args}" } rescue StandardError nil end |
.load_config(path, profile = nil) ⇒ Object
15 16 17 18 19 20 21 22 23 24 25 |
# File 'lib/mcp_diff/config.rb', line 15 def load_config(path, profile = nil) text = read_config_text(path) begin config = JSON.parse(text) rescue JSON::ParserError => e raise "config \"#{path}\" is not valid JSON: #{e.}" end config = apply_profile(config, profile) unless profile.nil? validate_config(path, config) config end |
.read_config_text(path) ⇒ Object
27 28 29 30 31 |
# File 'lib/mcp_diff/config.rb', line 27 def read_config_text(path) File.read(path) rescue SystemCallError raise "cannot read config \"#{path}\" — run: npx @mcp-diff/cli init" end |
.shell_split(command) ⇒ Object
Split a command line into argv, honoring single/double quotes.
88 89 90 |
# File 'lib/mcp_diff/config.rb', line 88 def shell_split(command) command.scan(/"([^"]*)"|'([^']*)'|(\S+)/).map { |dq, sq, | dq || sq || } end |
.validate_config(path, config) ⇒ Object
46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/mcp_diff/config.rb', line 46 def validate_config(path, config) invalid = ->(msg) { raise "config \"#{path}\": #{msg}" } validate_server(config["server"], invalid) if config.key?("server") invalid.call('"lockfile" must be a string path') if config.key?("lockfile") && !config["lockfile"].is_a?(String) validate_timeout(config["timeoutMs"], invalid) if config.key?("timeoutMs") validate_ignore(config["ignore"], invalid) if config.key?("ignore") if config.key?("strict") && !%w[breaking behavior].include?(config["strict"]) invalid.call('"strict" must be "breaking" or "behavior"') end validate_env(config["env"], invalid) if config.key?("env") end |
.validate_env(value, invalid) ⇒ Object
82 83 84 85 |
# File 'lib/mcp_diff/config.rb', line 82 def validate_env(value, invalid) ok = value.is_a?(Hash) && value.values.all?(String) invalid.call('"env" must be an object of string values') unless ok end |
.validate_ignore(value, invalid) ⇒ Object
78 79 80 |
# File 'lib/mcp_diff/config.rb', line 78 def validate_ignore(value, invalid) invalid.call('"ignore" must be an array of string patterns') unless value.is_a?(Array) && value.all?(String) end |
.validate_server(server, invalid) ⇒ Object
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/mcp_diff/config.rb', line 58 def validate_server(server, invalid) invalid.call('"server" must be an object with "stdio" or "url"') unless server.is_a?(Hash) if server.key?("stdio") && !server["stdio"].is_a?(String) invalid.call('"server.stdio" must be a string command line') end return unless server.key?("url") invalid.call('"server.url" must be a string') unless server["url"].is_a?(String) begin u = URI.parse(server["url"]) invalid.call("\"server.url\" is not a valid URL: #{server['url']}") unless u.scheme && u.host rescue URI::InvalidURIError invalid.call("\"server.url\" is not a valid URL: #{server['url']}") end end |
.validate_timeout(value, invalid) ⇒ Object
74 75 76 |
# File 'lib/mcp_diff/config.rb', line 74 def validate_timeout(value, invalid) invalid.call('"timeoutMs" must be a positive number') unless value.is_a?(Numeric) && value.finite? && value.positive? end |