Class: MCPClient::ConfigParser

Inherits:
Object
  • Object
show all
Defined in:
lib/mcp_client/config_parser.rb

Overview

Parses MCP server definition JSON files into configuration hashes

Constant Summary collapse

RESERVED_KEYS =

Reserved JSON keys that shouldn’t be included in final config

%w[comment description].freeze

Instance Method Summary collapse

Constructor Details

#initialize(file_path, logger: nil) ⇒ ConfigParser



14
15
16
17
# File 'lib/mcp_client/config_parser.rb', line 14

def initialize(file_path, logger: nil)
  @file_path = file_path
  @logger = logger || Logger.new($stdout, level: Logger::WARN)
end

Instance Method Details

#determine_server_type(config, server_name) ⇒ String?

Determine the type of server from its configuration



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/mcp_client/config_parser.rb', line 98

def determine_server_type(config, server_name)
  type = config['type']
  return type if type

  inferred_type = if config.key?('command') || config.key?('args') || config.key?('env')
                    'stdio'
                  elsif config.key?('url')
                    'sse'
                  end

  if inferred_type
    @logger.warn("'type' not specified for server '#{server_name}', inferring as '#{inferred_type}'.")
    return inferred_type
  end

  @logger.warn("Could not determine type for server '#{server_name}' (missing 'command' or 'url'); skipping.")
  nil
end

#extract_servers_data(data) ⇒ Hash

Extract server data from parsed JSON



46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/mcp_client/config_parser.rb', line 46

def extract_servers_data(data)
  if data.is_a?(Hash) && data.key?('mcpServers') && data['mcpServers'].is_a?(Hash)
    data['mcpServers']
  elsif data.is_a?(Array)
    h = {}
    data.each_with_index { |cfg, idx| h[idx.to_s] = cfg }
    h
  elsif data.is_a?(Hash)
    { '0' => data }
  else
    @logger.warn("Invalid root JSON structure in #{@file_path}: #{data.class}")
    {}
  end
end

#parseHash<String, Hash>

Parse the JSON config and return a mapping of server names to clean config hashes



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/mcp_client/config_parser.rb', line 21

def parse
  content = File.read(@file_path)
  data = JSON.parse(content)

  servers_data = extract_servers_data(data)
  servers_data = filter_reserved_keys(servers_data)

  result = {}
  servers_data.each do |server_name, config|
    next unless validate_server_config(config, server_name)

    server_config = process_server_config(config, server_name)
    result[server_name] = server_config if server_config
  end

  result
rescue Errno::ENOENT
  raise Errno::ENOENT, "Server definition file not found: #{@file_path}"
rescue JSON::ParserError => e
  raise JSON::ParserError, "Invalid JSON in #{@file_path}: #{e.message}"
end

#process_server_config(config, server_name) ⇒ Hash?

Process a single server configuration



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/mcp_client/config_parser.rb', line 76

def process_server_config(config, server_name)
  type = determine_server_type(config, server_name)
  return nil unless type

  clean = { type: type.to_s }
  case type.to_s
  when 'stdio'
    parse_stdio_config(clean, config, server_name)
  when 'sse'
    return nil unless parse_sse_config(clean, config, server_name)
  else
    @logger.warn("Unrecognized type '#{type}' for server '#{server_name}'; skipping.")
    return nil
  end

  clean
end

#validate_server_config(config, server_name) ⇒ Boolean

Validate server configuration is a hash



65
66
67
68
69
70
# File 'lib/mcp_client/config_parser.rb', line 65

def validate_server_config(config, server_name)
  return true if config.is_a?(Hash)

  @logger.warn("Configuration for server '#{server_name}' is not an object; skipping.")
  false
end