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

Returns a new instance of ConfigParser.

Parameters:

  • file_path (String)

    path to JSON file containing ‘mcpServers’ definitions

  • logger (Logger, nil) (defaults to: nil)

    optional logger for warnings



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

Parameters:

  • config (Hash)

    server configuration

  • server_name (String)

    name of the server for logging

Returns:

  • (String, nil)

    determined server type or nil if cannot be determined



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/mcp_client/config_parser.rb', line 106

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')
                    # Default to streamable_http unless URL contains "sse"
                    url = config['url'].to_s.downcase
                    url.include?('sse') ? 'sse' : 'streamable_http'
                  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

Parameters:

  • data (Object)

    parsed JSON data

Returns:

  • (Hash)

    normalized server data



50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/mcp_client/config_parser.rb', line 50

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

Returns:

  • (Hash<String, Hash>)

    server name => config hash with symbol keys



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 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 valid_server_config?(config, server_name)

    server_config = process_server_config(config, server_name)
    next unless server_config

    # Add server name to the config
    server_config[:name] = server_name
    result[server_name] = 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

Parameters:

  • config (Hash)

    server configuration to process

  • server_name (String)

    name of the server

Returns:

  • (Hash, nil)

    processed configuration or nil if invalid



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/mcp_client/config_parser.rb', line 80

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)
  when 'streamable_http'
    return nil unless parse_streamable_http_config?(clean, config, server_name)
  when 'http'
    return nil unless parse_http_config?(clean, config, server_name)
  else
    @logger.warn("Unrecognized type '#{type}' for server '#{server_name}'; skipping.")
    return nil
  end

  clean
end

#valid_server_config?(config, server_name) ⇒ Boolean

Validate server configuration is a hash

Parameters:

  • config (Object)

    server configuration to validate

  • server_name (String)

    name of the server

Returns:

  • (Boolean)

    true if valid, false otherwise



69
70
71
72
73
74
# File 'lib/mcp_client/config_parser.rb', line 69

def valid_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