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,211 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'mcp'
|
4
|
+
require 'json'
|
5
|
+
require 'net/http'
|
6
|
+
require_relative 'api_client'
|
7
|
+
require_relative 'logging'
|
8
|
+
require_relative 'config'
|
9
|
+
|
10
|
+
module SwaggerMCPTool
|
11
|
+
# Registry for managing dynamic MCP tools
|
12
|
+
class ToolRegistry
|
13
|
+
include Singleton
|
14
|
+
|
15
|
+
attr_accessor :dynamic_tools
|
16
|
+
|
17
|
+
def initialize(config = nil, logger = nil)
|
18
|
+
@dynamic_tools = []
|
19
|
+
@tool_names = Set.new
|
20
|
+
@logger = logger || config&.logger || Logger.new($stdout)
|
21
|
+
end
|
22
|
+
|
23
|
+
def register_dynamic_tools(tools, base_url, _swagger_spec = nil)
|
24
|
+
ensure_setup!
|
25
|
+
@dynamic_tools = @config.tools if @config&.tools
|
26
|
+
validate_inputs!(tools, base_url)
|
27
|
+
@logger.info "Registering #{tools.size} dynamic tools"
|
28
|
+
|
29
|
+
tools.each do |tool_def|
|
30
|
+
register_tool(tool_def, base_url) unless tool_exists?(tool_def['name'])
|
31
|
+
end
|
32
|
+
|
33
|
+
@logger.info "Successfully registered #{@dynamic_tools.size} tools"
|
34
|
+
@dynamic_tools.size
|
35
|
+
end
|
36
|
+
|
37
|
+
def setup(config = nil, logger = nil)
|
38
|
+
return if @initialized
|
39
|
+
|
40
|
+
@config = config
|
41
|
+
@logger = logger || config&.logger || Logger.new($stdout)
|
42
|
+
@initialized = true
|
43
|
+
@tool_names = Set.new
|
44
|
+
@dynamic_tools = []
|
45
|
+
|
46
|
+
@logger.info 'ToolRegistry singleton initialized'
|
47
|
+
end
|
48
|
+
|
49
|
+
# Guard method to ensure setup is called
|
50
|
+
def ensure_setup!
|
51
|
+
return if @initialized
|
52
|
+
|
53
|
+
raise 'ToolRegistry must be setup before use. Call ToolRegistry.instance.setup(config, logger)'
|
54
|
+
end
|
55
|
+
|
56
|
+
def execute_tool(context, args, server_context)
|
57
|
+
execute_tool_with_context(context, args, server_context)
|
58
|
+
end
|
59
|
+
|
60
|
+
def create_mcp_tool(tool_def, base_url)
|
61
|
+
schema = build_clean_schema(tool_def['parameters'])
|
62
|
+
|
63
|
+
execution_context = create_execution_context(tool_def, base_url)
|
64
|
+
registry_instance = self
|
65
|
+
|
66
|
+
MCP::Tool.define(
|
67
|
+
name: tool_def['name'],
|
68
|
+
description: tool_def['description'],
|
69
|
+
input_schema: schema,
|
70
|
+
annotations: build_annotations(tool_def['name'], tool_def['method'])
|
71
|
+
) do |args = {}, server_context = {}|
|
72
|
+
registry_instance.execute_tool(execution_context, args, server_context)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def prepare_execution_state(context, args, server_context)
|
79
|
+
{
|
80
|
+
tool_name: context[:tool_name],
|
81
|
+
method: context[:method],
|
82
|
+
path: context[:path],
|
83
|
+
base_url: context[:base_url],
|
84
|
+
logger: context[:logger],
|
85
|
+
args: args,
|
86
|
+
server_context: extract_server_context(args, server_context)
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
def tool_exists?(tool_name)
|
91
|
+
@tool_names.include?(tool_name)
|
92
|
+
end
|
93
|
+
|
94
|
+
def validate_inputs!(tools, base_url)
|
95
|
+
raise ArgumentError, 'Tools must be an array' unless tools.is_a?(Array)
|
96
|
+
raise ArgumentError, 'Base URL cannot be empty' if base_url.nil? || base_url.strip.empty?
|
97
|
+
end
|
98
|
+
|
99
|
+
def register_tool(tool_def, base_url)
|
100
|
+
tool_name = tool_def['name']
|
101
|
+
mcp_tool = create_mcp_tool(tool_def, base_url)
|
102
|
+
@dynamic_tools << mcp_tool
|
103
|
+
@tool_names.add(tool_name)
|
104
|
+
|
105
|
+
@logger.info "Registered tool: #{tool_name}"
|
106
|
+
rescue StandardError => e
|
107
|
+
@logger.error "Failed to register #{tool_name}: #{e.message}"
|
108
|
+
end
|
109
|
+
|
110
|
+
def create_execution_context(tool_def, base_url)
|
111
|
+
{
|
112
|
+
tool_name: tool_def['name'],
|
113
|
+
method: tool_def['method'],
|
114
|
+
path: tool_def['path'],
|
115
|
+
base_url: base_url,
|
116
|
+
logger: @logger,
|
117
|
+
config: @config
|
118
|
+
}
|
119
|
+
end
|
120
|
+
|
121
|
+
def build_clean_schema(parameters)
|
122
|
+
return { type: 'object', properties: {}, required: [] } unless parameters
|
123
|
+
|
124
|
+
properties = clean_properties(parameters['properties'] || {})
|
125
|
+
required = filter_required_fields(parameters['required'] || [], properties)
|
126
|
+
|
127
|
+
{
|
128
|
+
type: 'object',
|
129
|
+
properties: properties,
|
130
|
+
required: required
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
def clean_properties(original_properties)
|
135
|
+
original_properties.each_with_object({}) do |(prop_name, prop_def), clean_props|
|
136
|
+
if valid_property?(prop_def)
|
137
|
+
clean_props[prop_name] = prop_def
|
138
|
+
else
|
139
|
+
@logger.warn "Invalid property #{prop_name}, skipping"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def valid_property?(prop_def)
|
145
|
+
prop_def.is_a?(Hash) && prop_def['type']
|
146
|
+
end
|
147
|
+
|
148
|
+
def filter_required_fields(required_fields, properties)
|
149
|
+
required_fields.select { |field| properties.key?(field) }
|
150
|
+
end
|
151
|
+
|
152
|
+
def build_annotations(tool_name, tool_method)
|
153
|
+
method_lower = tool_method.downcase
|
154
|
+
|
155
|
+
{
|
156
|
+
title: tool_name,
|
157
|
+
read_only_hint: method_lower == 'get',
|
158
|
+
destructive_hint: destructive_method?(method_lower),
|
159
|
+
idempotent_hint: idempotent_method?(method_lower),
|
160
|
+
open_world_hint: false
|
161
|
+
}
|
162
|
+
end
|
163
|
+
|
164
|
+
def destructive_method?(method)
|
165
|
+
%w[delete put patch].include?(method)
|
166
|
+
end
|
167
|
+
|
168
|
+
def idempotent_method?(method)
|
169
|
+
%w[get put delete].include?(method)
|
170
|
+
end
|
171
|
+
|
172
|
+
def execute_tool_with_context(context, args, server_context)
|
173
|
+
execution_state = prepare_execution_state(context, args, server_context)
|
174
|
+
log_execution_start(execution_state)
|
175
|
+
|
176
|
+
response = make_api_request(execution_state)
|
177
|
+
|
178
|
+
log_execution_success(execution_state)
|
179
|
+
response
|
180
|
+
rescue StandardError => e
|
181
|
+
log_tool_execution_error(execution_state, e)
|
182
|
+
create_error_response(e.message)
|
183
|
+
end
|
184
|
+
|
185
|
+
def make_api_request(state)
|
186
|
+
@logger.debug "Making API request with state: #{state.inspect}"
|
187
|
+
api_client = SwaggerMCPTool::ApiClient.new(state[:base_url])
|
188
|
+
api_client.make_request(state[:method], state[:path], state[:args], state[:server_context])
|
189
|
+
end
|
190
|
+
|
191
|
+
def extract_server_context(args, server_context)
|
192
|
+
server_context.empty? ? args.delete(:server_context) || {} : server_context
|
193
|
+
end
|
194
|
+
|
195
|
+
def log_execution_success(state)
|
196
|
+
state[:logger].info "=== TOOL SUCCESS: #{state[:tool_name]} ==="
|
197
|
+
end
|
198
|
+
|
199
|
+
def log_execution_start(context)
|
200
|
+
context[:logger].info "=== EXECUTING TOOL: #{context[:tool_name]} ==="
|
201
|
+
context[:logger].info "Method: #{context[:method].upcase}, Path: #{context[:path]}"
|
202
|
+
context[:logger].info "Params: #{context[:args].inspect}"
|
203
|
+
end
|
204
|
+
|
205
|
+
def log_tool_execution_error(state, error)
|
206
|
+
state[:logger].error "=== TOOL ERROR: #{state[:tool_name]} ==="
|
207
|
+
state[:logger].error "Error: #{error.message}"
|
208
|
+
state[:logger].debug "Backtrace: #{error.backtrace.join("\n")}"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'swagger_mcp_tool/version'
|
2
|
+
require 'swagger_mcp_tool/config'
|
3
|
+
require 'swagger_mcp_tool/server'
|
4
|
+
require 'swagger_mcp_tool/swagger_client'
|
5
|
+
require 'swagger_mcp_tool/tool_generator'
|
6
|
+
require 'swagger_mcp_tool/api_client'
|
7
|
+
require 'swagger_mcp_tool/auth_handler'
|
8
|
+
|
9
|
+
module SwaggerMCPTool
|
10
|
+
class Error < StandardError; end
|
11
|
+
|
12
|
+
# Start the server with the current configuration
|
13
|
+
def self.start_server
|
14
|
+
Server.start
|
15
|
+
end
|
16
|
+
|
17
|
+
# Configure the server
|
18
|
+
def self.configure(&block)
|
19
|
+
Config.configure(&block)
|
20
|
+
end
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,221 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: swagger_mcp_tool
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ankit
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-08-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: json
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.12'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.12.2
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '2.12'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.12.2
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: mcp
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 0.1.0
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 0.1.0
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: puma
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '6.6'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '6.6'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: sinatra
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '4.1'
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: 4.1.1
|
71
|
+
type: :runtime
|
72
|
+
prerelease: false
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - "~>"
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '4.1'
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: 4.1.1
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: sinatra-contrib
|
83
|
+
requirement: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - "~>"
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '4.1'
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: 4.1.1
|
91
|
+
type: :runtime
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '4.1'
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: 4.1.1
|
101
|
+
- !ruby/object:Gem::Dependency
|
102
|
+
name: bundler
|
103
|
+
requirement: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - "~>"
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '2.6'
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 2.6.9
|
111
|
+
type: :development
|
112
|
+
prerelease: false
|
113
|
+
version_requirements: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '2.6'
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: 2.6.9
|
121
|
+
- !ruby/object:Gem::Dependency
|
122
|
+
name: rake
|
123
|
+
requirement: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - "~>"
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '13.3'
|
128
|
+
type: :development
|
129
|
+
prerelease: false
|
130
|
+
version_requirements: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - "~>"
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '13.3'
|
135
|
+
- !ruby/object:Gem::Dependency
|
136
|
+
name: rspec
|
137
|
+
requirement: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - "~>"
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '3.13'
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: 3.13.1
|
145
|
+
type: :development
|
146
|
+
prerelease: false
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - "~>"
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '3.13'
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: 3.13.1
|
155
|
+
- !ruby/object:Gem::Dependency
|
156
|
+
name: yard
|
157
|
+
requirement: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - "~>"
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: '0.9'
|
162
|
+
type: :development
|
163
|
+
prerelease: false
|
164
|
+
version_requirements: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - "~>"
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0.9'
|
169
|
+
description: A Model Context Protocol (MCP) server that generates tools from Swagger/OpenAPI
|
170
|
+
specifications
|
171
|
+
email:
|
172
|
+
- akisingh501@gmail.com
|
173
|
+
executables:
|
174
|
+
- swagger_mcp_server
|
175
|
+
extensions: []
|
176
|
+
extra_rdoc_files: []
|
177
|
+
files:
|
178
|
+
- README.md
|
179
|
+
- bin/console
|
180
|
+
- bin/setup
|
181
|
+
- bin/swagger_mcp_server
|
182
|
+
- lib/swagger_mcp_tool.rb
|
183
|
+
- lib/swagger_mcp_tool/api_client.rb
|
184
|
+
- lib/swagger_mcp_tool/auth_handler.rb
|
185
|
+
- lib/swagger_mcp_tool/config.rb
|
186
|
+
- lib/swagger_mcp_tool/helpers/parameter_processor.rb
|
187
|
+
- lib/swagger_mcp_tool/helpers/request.rb
|
188
|
+
- lib/swagger_mcp_tool/helpers/request_body_processor.rb
|
189
|
+
- lib/swagger_mcp_tool/helpers/swagger_validator.rb
|
190
|
+
- lib/swagger_mcp_tool/helpers/tool_builder.rb
|
191
|
+
- lib/swagger_mcp_tool/helpers/tool_register.rb
|
192
|
+
- lib/swagger_mcp_tool/logging.rb
|
193
|
+
- lib/swagger_mcp_tool/server.rb
|
194
|
+
- lib/swagger_mcp_tool/swagger_client.rb
|
195
|
+
- lib/swagger_mcp_tool/tool_generator.rb
|
196
|
+
- lib/swagger_mcp_tool/tool_registry.rb
|
197
|
+
- lib/swagger_mcp_tool/version.rb
|
198
|
+
homepage: https://github.com/yourusername/swagger_mcp_tool
|
199
|
+
licenses:
|
200
|
+
- MIT
|
201
|
+
metadata: {}
|
202
|
+
post_install_message:
|
203
|
+
rdoc_options: []
|
204
|
+
require_paths:
|
205
|
+
- lib
|
206
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
207
|
+
requirements:
|
208
|
+
- - ">="
|
209
|
+
- !ruby/object:Gem::Version
|
210
|
+
version: 3.2.2
|
211
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - ">="
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: '0'
|
216
|
+
requirements: []
|
217
|
+
rubygems_version: 3.4.10
|
218
|
+
signing_key:
|
219
|
+
specification_version: 4
|
220
|
+
summary: MCP server for Swagger/OpenAPI specifications
|
221
|
+
test_files: []
|