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.
@@ -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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwaggerMcpTool
4
+ VERSION = '0.1.1'
5
+ 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: []