Module: ZeroMcp::OpenApi

Defined in:
lib/zeromcp/openapi.rb

Class Method Summary collapse

Class Method Details

.build(tools, config) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
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
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/zeromcp/openapi.rb', line 9

def build(tools, config)
  paths = {}

  tools.each do |_name, tool|
    next unless tool.route.is_a?(Hash)

    route_method = tool.route_method
    route_path = tool.route_path

    # Extract :param names from path, convert to {param} for OpenAPI
    path_param_names = route_path.scan(/:([A-Za-z_][A-Za-z0-9_]*)/).flatten
    openapi_path = route_path.gsub(/:([A-Za-z_][A-Za-z0-9_]*)/, '{\1}')

    input = tool.input || {}
    operation = {
      'operationId' => tool.name,
      'description' => tool.description || '',
      'responses'   => {
        '200' => { 'description' => 'Success' },
        '500' => { 'description' => 'Error' }
      }
    }

    if route_method == 'GET'
      operation['parameters'] = build_parameters(input, path_param_names)
    else
      operation['requestBody'] = build_request_body(input, path_param_names)
      unless path_param_names.empty?
        operation['parameters'] = path_param_names.map do |name|
          {
            'name'     => name,
            'in'       => 'path',
            'required' => true,
            'schema'   => { 'type' => 'string' }
          }
        end
      end
    end

    paths[openapi_path] ||= {}
    paths[openapi_path][route_method.downcase] = operation
  end

  {
    'openapi' => '3.0.0',
    'info'    => { 'title' => config.title, 'version' => '0.5.0' },
    'paths'   => paths
  }
end

.build_parameters(input, path_param_names) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/zeromcp/openapi.rb', line 59

def build_parameters(input, path_param_names)
  params = []

  # Path params first (preserve order from path)
  path_param_names.each do |name|
    spec = field_to_openapi_schema(input[name] || input[name.to_sym])
    params << {
      'name'     => name,
      'in'       => 'path',
      'required' => true,
      'schema'   => spec
    }
  end

  # Remaining fields as query params
  input.each do |key, value|
    key_s = key.to_s
    next if path_param_names.include?(key_s)

    spec     = field_to_openapi_schema(value)
    optional = value.is_a?(Hash) && (value[:optional] || value['optional'])
    params << {
      'name'     => key_s,
      'in'       => 'query',
      'required' => !optional,
      'schema'   => spec
    }
  end

  params
end

.build_request_body(input, path_param_names) ⇒ Object



91
92
93
94
95
96
97
98
99
100
# File 'lib/zeromcp/openapi.rb', line 91

def build_request_body(input, path_param_names)
  body_input = input.reject { |key, _| path_param_names.include?(key.to_s) }
  schema = Schema.to_json_schema(body_input)
  {
    'required' => true,
    'content'  => {
      'application/json' => { 'schema' => schema }
    }
  }
end

.field_to_openapi_schema(value) ⇒ Object



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/zeromcp/openapi.rb', line 102

def field_to_openapi_schema(value)
  return { 'type' => 'string' } if value.nil?

  if value.is_a?(String)
    Schema::TYPE_MAP[value] || { 'type' => 'string' }
  elsif value.is_a?(Hash)
    type = value[:type] || value['type']
    mapped = Schema::TYPE_MAP[type.to_s] || { 'type' => 'string' }
    spec = mapped.dup
    desc = value[:description] || value['description']
    spec['description'] = desc if desc
    spec
  else
    { 'type' => 'string' }
  end
end