swagger_mcp_tool 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +594 -0
- data/bin/console +11 -0
- data/bin/setup +8 -0
- data/bin/swagger_mcp_server +45 -0
- data/lib/swagger_mcp_tool/api_client.rb +154 -0
- data/lib/swagger_mcp_tool/auth_handler.rb +82 -0
- data/lib/swagger_mcp_tool/config.rb +143 -0
- data/lib/swagger_mcp_tool/helpers/parameter_processor.rb +84 -0
- data/lib/swagger_mcp_tool/helpers/request.rb +114 -0
- data/lib/swagger_mcp_tool/helpers/request_body_processor.rb +66 -0
- data/lib/swagger_mcp_tool/helpers/swagger_validator.rb +42 -0
- data/lib/swagger_mcp_tool/helpers/tool_builder.rb +43 -0
- data/lib/swagger_mcp_tool/helpers/tool_register.rb +40 -0
- data/lib/swagger_mcp_tool/logging.rb +61 -0
- data/lib/swagger_mcp_tool/server.rb +254 -0
- data/lib/swagger_mcp_tool/swagger_client.rb +116 -0
- data/lib/swagger_mcp_tool/tool_generator.rb +85 -0
- data/lib/swagger_mcp_tool/tool_registry.rb +211 -0
- data/lib/swagger_mcp_tool/version.rb +5 -0
- data/lib/swagger_mcp_tool.rb +21 -0
- metadata +221 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SwaggerMCPTool
|
4
|
+
module Helpers
|
5
|
+
module ToolBuilder
|
6
|
+
private
|
7
|
+
|
8
|
+
def create_base_tool_definition(path, method, operation)
|
9
|
+
{
|
10
|
+
'name' => generate_operation_id(path, method, operation),
|
11
|
+
'description' => generate_description(path, method, operation),
|
12
|
+
'method' => method,
|
13
|
+
'path' => path,
|
14
|
+
'parameters' => create_empty_parameters_schema
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def generate_operation_id(path, method, operation)
|
19
|
+
operation['operationId'] || generate_default_operation_id(path, method)
|
20
|
+
end
|
21
|
+
|
22
|
+
def generate_default_operation_id(path, method)
|
23
|
+
"#{method}_#{sanitize_path_for_id(path)}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def sanitize_path_for_id(path)
|
27
|
+
path.gsub(/[^\w]/, '_')
|
28
|
+
end
|
29
|
+
|
30
|
+
def generate_description(path, method, operation)
|
31
|
+
operation['summary'] || operation['description'] || "#{method.upcase} #{path}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_empty_parameters_schema
|
35
|
+
{
|
36
|
+
'type' => 'object',
|
37
|
+
'properties' => {},
|
38
|
+
'required' => []
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SwaggerMCPTool
|
4
|
+
module Helpers
|
5
|
+
module ToolRegister
|
6
|
+
def tool_registry
|
7
|
+
SwaggerMCPTool::ToolRegistry.instance
|
8
|
+
end
|
9
|
+
|
10
|
+
def setup_tool_registry
|
11
|
+
registry = tool_registry
|
12
|
+
registry.setup(@config) # <-- This is where setup gets called
|
13
|
+
@logger = @config.logger
|
14
|
+
log_message('Tool registry setup complete')
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize_tools
|
18
|
+
generate_tools_from_swagger_url
|
19
|
+
end
|
20
|
+
|
21
|
+
# Generate tools from a Swagger URL
|
22
|
+
def generate_tools_from_swagger_url
|
23
|
+
@config.logger.info "Generating tools from Swagger URL: #{@config.swagger_url}"
|
24
|
+
# Create a Swagger client
|
25
|
+
swagger_client = SwaggerClient.new(@config.swagger_url)
|
26
|
+
|
27
|
+
# Create a tool generator
|
28
|
+
tool_generator = ToolGenerator.new(swagger_client)
|
29
|
+
|
30
|
+
# Generate tools
|
31
|
+
tools = tool_generator.generate_tools
|
32
|
+
|
33
|
+
# Register the tools dynamically (simplified)
|
34
|
+
tool_registry.register_dynamic_tools(tools, swagger_client.base_url)
|
35
|
+
rescue StandardError => e
|
36
|
+
log_and_raise_error(e)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The SwaggerMCPTool module provides core functionality for the Swagger MCP Tool gem.
|
4
|
+
# It serves as a namespace for organizing related components, such as logging utilities,
|
5
|
+
# configuration management, and integration helpers for managing Swagger-based MCP services.
|
6
|
+
|
7
|
+
module SwaggerMCPTool
|
8
|
+
# SwaggerMCPTool::Logging for logging-related methods and usage.
|
9
|
+
module Logging
|
10
|
+
def log_server_initialization
|
11
|
+
logger.info '=== SERVER INITIALIZATION ==='
|
12
|
+
|
13
|
+
server_config_items.each do |label, value|
|
14
|
+
logger.info "#{label}: #{value}"
|
15
|
+
end
|
16
|
+
|
17
|
+
logger.info '================================'
|
18
|
+
end
|
19
|
+
|
20
|
+
def log_and_raise_error(exception)
|
21
|
+
logger.error(exception.message)
|
22
|
+
raise exception
|
23
|
+
end
|
24
|
+
|
25
|
+
def log_message(message)
|
26
|
+
logger.info(message)
|
27
|
+
end
|
28
|
+
|
29
|
+
def log_request_details(context)
|
30
|
+
logger.debug 'API Request Details:'
|
31
|
+
logger.debug " Method: #{context[:method].upcase}"
|
32
|
+
logger.debug " Path: #{context[:original_path]}"
|
33
|
+
logger.debug " Params: #{context[:params].inspect}"
|
34
|
+
logger.debug " Headers: #{sanitize_headers_for_logging(context[:headers])}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def log_request_execution(method, uri)
|
38
|
+
logger.info "Making #{method.upcase} request to #{uri.host}#{uri.path}"
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def server_config_items
|
44
|
+
[
|
45
|
+
['Server port', config.server_port],
|
46
|
+
['Server bind', config.server_bind],
|
47
|
+
['Swagger URL', config.swagger_url],
|
48
|
+
['MCP name', config.mcp_name],
|
49
|
+
['Auth header name', config.auth_header_name]
|
50
|
+
]
|
51
|
+
end
|
52
|
+
|
53
|
+
def logger
|
54
|
+
config.logger
|
55
|
+
end
|
56
|
+
|
57
|
+
def config
|
58
|
+
@config
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,254 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sinatra/base'
|
4
|
+
require 'sinatra/json'
|
5
|
+
require 'mcp'
|
6
|
+
require 'json'
|
7
|
+
require 'logger'
|
8
|
+
require 'byebug'
|
9
|
+
require_relative 'tool_registry'
|
10
|
+
require_relative 'logging'
|
11
|
+
require_relative 'config'
|
12
|
+
require_relative 'helpers/tool_register'
|
13
|
+
|
14
|
+
module SwaggerMCPTool
|
15
|
+
# The Server class is the main Sinatra-based HTTP server for the SwaggerMCPTool.
|
16
|
+
# It provides endpoints for manifest retrieval, tool generation, authentication token management,
|
17
|
+
# and health checks. The server dynamically generates tools from a Swagger/OpenAPI specification,
|
18
|
+
# manages user authentication, and integrates with the MCP (Model Control Protocol) server.
|
19
|
+
#
|
20
|
+
# Key responsibilities:
|
21
|
+
# - Initializes configuration and tool registry on startup.
|
22
|
+
# - Exposes RESTful endpoints for manifest, MCP requests, tool generation, and health checks.
|
23
|
+
# - Handles CORS and server configuration.
|
24
|
+
# - Dynamically generates and registers tools from a Swagger URL.
|
25
|
+
# - Manages user authentication tokens and server context.
|
26
|
+
#
|
27
|
+
# Endpoints:
|
28
|
+
# - GET /mcp/manifest : Returns the MCP manifest for integration.
|
29
|
+
# - POST /mcp : Handles MCP requests with dynamic tools.
|
30
|
+
# - POST /set_auth_token : Sets authentication tokens for users.
|
31
|
+
# - POST /generate_tools : Generates tools from a provided Swagger URL.
|
32
|
+
# - GET /logo.png : Serves the logo image.
|
33
|
+
# - GET /health : Health check and server status.
|
34
|
+
#
|
35
|
+
# Usage:
|
36
|
+
# SwaggerMCPTool::Server.start
|
37
|
+
#
|
38
|
+
# Dependencies:
|
39
|
+
# - Sinatra::Base
|
40
|
+
# - SwaggerMCPTool::Config
|
41
|
+
# - SwaggerMCPTool::ToolRegistry
|
42
|
+
# - SwaggerClient
|
43
|
+
# - ToolGenerator
|
44
|
+
# - MCP::Server
|
45
|
+
#
|
46
|
+
# Configuration is managed via the Config singleton, and tools are registered
|
47
|
+
# dynamically via the ToolRegistry singleton.
|
48
|
+
class Server < Sinatra::Base
|
49
|
+
attr_reader :config, :dynamic_tools
|
50
|
+
|
51
|
+
include Logging
|
52
|
+
include Helpers::ToolRegister
|
53
|
+
|
54
|
+
# Initialize with configuration
|
55
|
+
def initialize(app = nil)
|
56
|
+
super(app)
|
57
|
+
@config = Config.instance
|
58
|
+
|
59
|
+
# Debug configuration values
|
60
|
+
log_server_initialization
|
61
|
+
# Generate tools on startup
|
62
|
+
setup_tool_registry
|
63
|
+
initialize_tools
|
64
|
+
# Configure server settings
|
65
|
+
configure_server
|
66
|
+
end
|
67
|
+
|
68
|
+
# def tool_registry
|
69
|
+
# SwaggerMCPTool::ToolRegistry.instance
|
70
|
+
# end
|
71
|
+
|
72
|
+
# def setup_tool_registry
|
73
|
+
# registry = tool_registry
|
74
|
+
# registry.setup(@config) # <-- This is where setup gets called
|
75
|
+
# @logger = @config.logger
|
76
|
+
# log_message('Tool registry setup complete')
|
77
|
+
# end
|
78
|
+
|
79
|
+
# def initialize_tools
|
80
|
+
# generate_tools_from_swagger_url
|
81
|
+
# end
|
82
|
+
|
83
|
+
# Start the server
|
84
|
+
def self.start
|
85
|
+
# self.define_routes
|
86
|
+
new
|
87
|
+
run!
|
88
|
+
end
|
89
|
+
|
90
|
+
def configure_server
|
91
|
+
self.class.set :server, @config.server_type
|
92
|
+
self.class.set :bind, @config.server_bind
|
93
|
+
self.class.set :port, @config.server_port
|
94
|
+
|
95
|
+
# Enable CORS
|
96
|
+
enable_cors
|
97
|
+
|
98
|
+
# Add options headers
|
99
|
+
add_options
|
100
|
+
end
|
101
|
+
|
102
|
+
def enable_cors
|
103
|
+
self.class.before do
|
104
|
+
headers 'Access-Control-Allow-Origin' => '*',
|
105
|
+
'Access-Control-Allow-Methods' => 'GET, POST, OPTIONS',
|
106
|
+
'Access-Control-Allow-Headers' => 'Content-Type, Authorization, X-User-ID, Auth-Token, X-Username, X-Email'
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def add_options
|
111
|
+
self.class.options '*' do
|
112
|
+
response.headers['Allow'] = 'GET, POST, OPTIONS'
|
113
|
+
response.headers['Access-Control-Allow-Headers'] =
|
114
|
+
'Authorization, Content-Type, Accept, X-User-ID, Auth-Token, X-Username, X-Email'
|
115
|
+
200
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# private
|
120
|
+
|
121
|
+
# Define server routes
|
122
|
+
# def define_routes
|
123
|
+
# MCP manifest endpoint
|
124
|
+
get '/mcp/manifest' do
|
125
|
+
manifest = {
|
126
|
+
schema_version: 'v1',
|
127
|
+
name_for_human: @config.mcp_name_for_human,
|
128
|
+
name_for_model: @config.mcp_name,
|
129
|
+
description_for_human: @config.mcp_description_for_human,
|
130
|
+
description_for_model: @config.mcp_description_for_model,
|
131
|
+
auth: {
|
132
|
+
type: @config.auth_type
|
133
|
+
},
|
134
|
+
api: {
|
135
|
+
type: 'function'
|
136
|
+
},
|
137
|
+
logo_url: "#{request.base_url}/logo.png",
|
138
|
+
contact_email: 'support@example.com',
|
139
|
+
legal_info_url: 'https://example.com/legal'
|
140
|
+
}
|
141
|
+
|
142
|
+
json manifest
|
143
|
+
end
|
144
|
+
|
145
|
+
# MCP endpoint
|
146
|
+
post '/mcp' do
|
147
|
+
content_type :json
|
148
|
+
|
149
|
+
# Create server context with user details
|
150
|
+
user_details = @config.to_context(request)
|
151
|
+
|
152
|
+
# Create MCP server with our tools and prompts
|
153
|
+
mcp = MCP::Server.new(
|
154
|
+
name: @config.mcp_name,
|
155
|
+
tools: tool_registry.dynamic_tools,
|
156
|
+
prompts: @config.prompts,
|
157
|
+
server_context: user_details
|
158
|
+
)
|
159
|
+
|
160
|
+
MCP.configure do |config|
|
161
|
+
config.instrumentation_callback = lambda { |data|
|
162
|
+
@config.logger.debug "Got instrumentation data #{data.inspect}"
|
163
|
+
}
|
164
|
+
end
|
165
|
+
# Process the MCP request
|
166
|
+
request_body = request.body.read
|
167
|
+
@config.logger.debug "MCP request: #{request_body}"
|
168
|
+
|
169
|
+
response_body = mcp.handle_json(request_body)
|
170
|
+
@config.logger.debug "MCP response: #{response_body}"
|
171
|
+
|
172
|
+
response_body
|
173
|
+
end
|
174
|
+
|
175
|
+
# Set auth token endpoint
|
176
|
+
post '/set_auth_token' do
|
177
|
+
content_type :json
|
178
|
+
|
179
|
+
begin
|
180
|
+
# Parse request body
|
181
|
+
request_data = JSON.parse(request.body.read)
|
182
|
+
user_id = request_data['user_id'] || request.env['HTTP_X_USER_ID'] || '1'
|
183
|
+
auth_token = request_data['auth_token']
|
184
|
+
|
185
|
+
if auth_token.nil? || auth_token.empty?
|
186
|
+
status 400
|
187
|
+
return { error: 'Missing auth_token parameter' }.to_json
|
188
|
+
end
|
189
|
+
|
190
|
+
# Set the auth token
|
191
|
+
@auth_handler.set_auth_token(user_id, auth_token)
|
192
|
+
|
193
|
+
# Return success
|
194
|
+
{
|
195
|
+
success: true,
|
196
|
+
message: "Auth token set for user: #{user_id}"
|
197
|
+
}.to_json
|
198
|
+
rescue StandardError => e
|
199
|
+
status 500
|
200
|
+
{ error: e.message }.to_json
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Generate tools endpoint
|
205
|
+
post '/generate_tools' do
|
206
|
+
content_type :json
|
207
|
+
|
208
|
+
# Parse request body
|
209
|
+
request_data = JSON.parse(request.body.read)
|
210
|
+
swagger_url = request_data['swagger_url']
|
211
|
+
|
212
|
+
if swagger_url.nil? || swagger_url.empty?
|
213
|
+
status 400
|
214
|
+
return { error: 'Missing swagger_url parameter' }.to_json
|
215
|
+
end
|
216
|
+
|
217
|
+
# Generate tools
|
218
|
+
generate_tools_from_swagger_url(swagger_url)
|
219
|
+
|
220
|
+
# Return success
|
221
|
+
{
|
222
|
+
success: true,
|
223
|
+
message: "Generated #{@dynamic_tools.size} tools",
|
224
|
+
tools: @dynamic_tools.map { |t| { name: t.name, description: t.description } }
|
225
|
+
}.to_json
|
226
|
+
rescue StandardError => e
|
227
|
+
status 500
|
228
|
+
{ error: e.message }.to_json
|
229
|
+
end
|
230
|
+
|
231
|
+
# Logo endpoint
|
232
|
+
get '/logo.png' do
|
233
|
+
# You can replace this with an actual logo file
|
234
|
+
content_type 'image/png'
|
235
|
+
File.read('logo.png') if File.exist?('logo.png')
|
236
|
+
end
|
237
|
+
|
238
|
+
# Health check endpoint
|
239
|
+
get '/health' do
|
240
|
+
content_type :json
|
241
|
+
{
|
242
|
+
status: 'ok',
|
243
|
+
timestamp: Time.now.to_s,
|
244
|
+
tools_count: @dynamic_tools.size,
|
245
|
+
prompts_count: @config.prompts.size,
|
246
|
+
cache_stats: @tool_cache.stats,
|
247
|
+
config: {
|
248
|
+
server_port: @config.server_port,
|
249
|
+
swagger_url: @config.swagger_url
|
250
|
+
}
|
251
|
+
}.to_json
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'uri'
|
4
|
+
require 'net/http'
|
5
|
+
require 'openssl'
|
6
|
+
require 'json'
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
module SwaggerMCPTool
|
10
|
+
# The SwaggerClient class is responsible for fetching and parsing Swagger/OpenAPI specifications
|
11
|
+
# from a given URL. It supports both Swagger 2.0 and OpenAPI 3.x formats, handling JSON and YAML
|
12
|
+
# content types. The class provides methods to extract the base URL from the specification and
|
13
|
+
# manages HTTP(S) requests with proper SSL configuration and error handling.
|
14
|
+
#
|
15
|
+
# Example usage:
|
16
|
+
# client = SwaggerMCPTool::SwaggerClient.new('https://example.com/swagger.json')
|
17
|
+
# spec = client.swagger_spec
|
18
|
+
# base_url = client.base_url
|
19
|
+
#
|
20
|
+
# Attributes:
|
21
|
+
# @swagger_spec [Hash] The parsed Swagger/OpenAPI specification.
|
22
|
+
# @base_url [String] The base URL extracted from the specification.
|
23
|
+
#
|
24
|
+
# Methods:
|
25
|
+
# - initialize(url): Initializes the client and fetches the specification.
|
26
|
+
# - fetch_swagger_spec(url): Fetches and parses the Swagger/OpenAPI spec from the URL.
|
27
|
+
# - parse_url_content(url, content): Parses the content as JSON or YAML.
|
28
|
+
# - get_base_url(swagger_spec): Determines the base URL from the spec.
|
29
|
+
# - get_base_for_swagger_v2(swagger_spec): Extracts base URL for Swagger 2.0.
|
30
|
+
# - get_base_for_swagger_v3(swagger_spec): Extracts base URL for OpenAPI 3.x.
|
31
|
+
# - make_http_request(url): Performs the HTTP GET request.
|
32
|
+
# - create_http_client(uri): Creates and configures the HTTP client.
|
33
|
+
# - configure_ssl(http, uri): Configures SSL for HTTPS requests.
|
34
|
+
# - validate_response!(response): Validates the HTTP response.
|
35
|
+
#
|
36
|
+
# @see https://swagger.io/specification/
|
37
|
+
class SwaggerClient
|
38
|
+
attr_reader :swagger_spec, :base_url
|
39
|
+
|
40
|
+
def initialize(url)
|
41
|
+
@config = Config.instance
|
42
|
+
@swagger_url = url
|
43
|
+
@swagger_spec = fetch_swagger_spec(url)
|
44
|
+
@base_url = get_base_url(@swagger_spec)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Fetch Swagger/OpenAPI specification from URL
|
48
|
+
def fetch_swagger_spec(url)
|
49
|
+
@config.logger.info "Fetching Swagger specification from: #{url}"
|
50
|
+
|
51
|
+
# Use HTTPS if specified in the URL
|
52
|
+
http_response = make_http_request(url)
|
53
|
+
validate_response!(http_response)
|
54
|
+
|
55
|
+
# Parse JSON or YAML based on content
|
56
|
+
parse_url_content(url, http_response.body)
|
57
|
+
end
|
58
|
+
|
59
|
+
def parse_url_content(url, content)
|
60
|
+
if url.end_with?('.json') || content.strip.start_with?('{')
|
61
|
+
JSON.parse(content)
|
62
|
+
else
|
63
|
+
YAML.safe_load(content)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Get base URL from Swagger spec
|
68
|
+
def get_base_url(swagger_spec)
|
69
|
+
if swagger_spec['swagger'] == '2.0'
|
70
|
+
get_base_for_swagger_v2(swagger_spec)
|
71
|
+
elsif swagger_spec['openapi']&.start_with?('3.')
|
72
|
+
get_base_for_swagger_v3(swagger_spec)
|
73
|
+
else
|
74
|
+
''
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def get_base_for_swagger_v2(swagger_spec)
|
79
|
+
base_path = swagger_spec['basePath'] || ''
|
80
|
+
host = swagger_spec['host'] || ''
|
81
|
+
schemes = swagger_spec['schemes'] || ['https']
|
82
|
+
"#{schemes.first}://#{host}#{base_path}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def get_base_for_swagger_v3(swagger_spec)
|
86
|
+
servers = swagger_spec['servers'] || [{ 'url' => '' }]
|
87
|
+
servers.first['url']
|
88
|
+
end
|
89
|
+
|
90
|
+
def make_http_request(url)
|
91
|
+
uri = URI(url)
|
92
|
+
http = create_http_client(uri)
|
93
|
+
request = Net::HTTP::Get.new(uri.request_uri)
|
94
|
+
http.request(request)
|
95
|
+
end
|
96
|
+
|
97
|
+
def create_http_client(uri)
|
98
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
99
|
+
configure_ssl(http, uri)
|
100
|
+
http
|
101
|
+
end
|
102
|
+
|
103
|
+
def configure_ssl(http, uri)
|
104
|
+
return unless uri.scheme == 'https'
|
105
|
+
|
106
|
+
http.use_ssl = true
|
107
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
108
|
+
end
|
109
|
+
|
110
|
+
def validate_response!(response)
|
111
|
+
return if response.is_a?(Net::HTTPSuccess)
|
112
|
+
|
113
|
+
raise "Failed to fetch Swagger specification: #{response.code} #{response.message}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'config'
|
4
|
+
require_relative 'logging'
|
5
|
+
require_relative 'helpers/swagger_validator'
|
6
|
+
require_relative 'helpers/parameter_processor'
|
7
|
+
require_relative 'helpers/request_body_processor'
|
8
|
+
require_relative 'helpers/tool_builder'
|
9
|
+
|
10
|
+
module SwaggerMCPTool
|
11
|
+
# Provides validation methods for Swagger (OpenAPI) specification structures.
|
12
|
+
# This module includes methods to validate the overall structure of a Swagger spec,
|
13
|
+
# ensure required keys are present, and check the structure of the 'paths' section.
|
14
|
+
# Intended to be included in classes that require Swagger spec validation logic.
|
15
|
+
#
|
16
|
+
# Example usage:
|
17
|
+
# include SwaggerValidator
|
18
|
+
# validate_swagger_spec!(swagger_spec_hash)
|
19
|
+
#
|
20
|
+
# Raises ArgumentError if the spec or paths are invalid.
|
21
|
+
#
|
22
|
+
# @see https://swagger.io/specification/
|
23
|
+
|
24
|
+
# Generate MCP tools from Swagger/OpenAPI specification
|
25
|
+
# Generates a list of tool definitions based on the Swagger/OpenAPI specification.
|
26
|
+
class ToolGenerator
|
27
|
+
include Helpers::SwaggerValidator
|
28
|
+
include Helpers::ParameterProcessor
|
29
|
+
include Helpers::RequestBodyProcessor
|
30
|
+
include Helpers::ToolBuilder
|
31
|
+
|
32
|
+
def initialize(swagger_client)
|
33
|
+
@swagger_client = swagger_client
|
34
|
+
@config = Config.instance
|
35
|
+
@generated_tools = []
|
36
|
+
end
|
37
|
+
|
38
|
+
def generate_tools
|
39
|
+
swagger_spec = @swagger_client.swagger_spec
|
40
|
+
validate_swagger_spec!(swagger_spec)
|
41
|
+
|
42
|
+
@config.logger.info 'Generating tools from Swagger spec'
|
43
|
+
|
44
|
+
process_swagger_paths(swagger_spec['paths'])
|
45
|
+
|
46
|
+
@config.logger.info "Generated #{@generated_tools.size} tools"
|
47
|
+
@generated_tools
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def process_swagger_paths(paths)
|
53
|
+
paths.each do |path, methods|
|
54
|
+
process_path_methods(path, methods) if methods.is_a?(Hash)
|
55
|
+
end
|
56
|
+
rescue StandardError => e
|
57
|
+
@config.logger.error "Error processing paths: #{e.message}"
|
58
|
+
@generated_tools
|
59
|
+
end
|
60
|
+
|
61
|
+
def process_path_methods(path, methods)
|
62
|
+
methods.each do |method, operation|
|
63
|
+
next if skip_method?(method, operation)
|
64
|
+
|
65
|
+
tool = build_tool_from_operation(path, method, operation)
|
66
|
+
@generated_tools << tool if tool
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def skip_method?(method, operation)
|
71
|
+
method == 'parameters' || !operation.is_a?(Hash)
|
72
|
+
end
|
73
|
+
|
74
|
+
def build_tool_from_operation(path, method, operation)
|
75
|
+
tool_definition = create_base_tool_definition(path, method, operation)
|
76
|
+
|
77
|
+
add_parameters_to_tool(tool_definition, operation)
|
78
|
+
add_request_body_to_tool(tool_definition, operation)
|
79
|
+
|
80
|
+
tool_definition
|
81
|
+
rescue StandardError => e
|
82
|
+
@config.logger.warn "Skipping tool for #{method.upcase} #{path}: #{e.message}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|