mcp-proxy-adapter 2.1.16__py3-none-any.whl → 3.0.0__py3-none-any.whl

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.
Files changed (84) hide show
  1. examples/__init__.py +19 -0
  2. examples/anti_patterns/README.md +51 -0
  3. examples/anti_patterns/__init__.py +9 -0
  4. examples/anti_patterns/bad_design/README.md +72 -0
  5. examples/anti_patterns/bad_design/global_state.py +170 -0
  6. examples/anti_patterns/bad_design/monolithic_command.py +272 -0
  7. examples/basic_example/README.md +131 -0
  8. examples/basic_example/__init__.py +8 -0
  9. examples/basic_example/commands/__init__.py +5 -0
  10. examples/basic_example/commands/echo_command.py +95 -0
  11. examples/basic_example/commands/math_command.py +151 -0
  12. examples/basic_example/commands/time_command.py +152 -0
  13. examples/basic_example/config.json +21 -0
  14. examples/basic_example/config.yaml +20 -0
  15. examples/basic_example/docs/EN/README.md +136 -0
  16. examples/basic_example/docs/RU/README.md +136 -0
  17. examples/basic_example/main.py +50 -0
  18. examples/basic_example/server.py +45 -0
  19. examples/basic_example/tests/conftest.py +243 -0
  20. examples/commands/echo_command.py +52 -0
  21. examples/commands/echo_result.py +65 -0
  22. examples/commands/get_date_command.py +98 -0
  23. examples/commands/new_uuid4_command.py +91 -0
  24. examples/complete_example/Dockerfile +24 -0
  25. examples/complete_example/README.md +92 -0
  26. examples/complete_example/__init__.py +8 -0
  27. examples/complete_example/commands/__init__.py +5 -0
  28. examples/complete_example/commands/system_command.py +327 -0
  29. examples/complete_example/config.json +41 -0
  30. examples/complete_example/configs/config.dev.yaml +40 -0
  31. examples/complete_example/configs/config.docker.yaml +40 -0
  32. examples/complete_example/docker-compose.yml +35 -0
  33. examples/complete_example/main.py +67 -0
  34. examples/complete_example/requirements.txt +20 -0
  35. examples/complete_example/server.py +85 -0
  36. examples/minimal_example/README.md +51 -0
  37. examples/minimal_example/__init__.py +8 -0
  38. examples/minimal_example/config.json +21 -0
  39. examples/minimal_example/config.yaml +26 -0
  40. examples/minimal_example/main.py +67 -0
  41. examples/minimal_example/simple_server.py +124 -0
  42. examples/minimal_example/tests/conftest.py +171 -0
  43. examples/minimal_example/tests/test_hello_command.py +111 -0
  44. examples/minimal_example/tests/test_integration.py +183 -0
  45. examples/server.py +69 -0
  46. examples/simple_server.py +137 -0
  47. examples/test_server.py +126 -0
  48. mcp_proxy_adapter/__init__.py +33 -1
  49. mcp_proxy_adapter/config.py +186 -0
  50. mcp_proxy_adapter/custom_openapi.py +125 -0
  51. mcp_proxy_adapter/framework.py +109 -0
  52. mcp_proxy_adapter/openapi.py +403 -0
  53. mcp_proxy_adapter/version.py +3 -0
  54. mcp_proxy_adapter-3.0.0.dist-info/METADATA +200 -0
  55. mcp_proxy_adapter-3.0.0.dist-info/RECORD +58 -0
  56. {mcp_proxy_adapter-2.1.16.dist-info → mcp_proxy_adapter-3.0.0.dist-info}/top_level.txt +1 -0
  57. mcp_proxy_adapter/adapter.py +0 -697
  58. mcp_proxy_adapter/analyzers/__init__.py +0 -1
  59. mcp_proxy_adapter/analyzers/docstring_analyzer.py +0 -199
  60. mcp_proxy_adapter/analyzers/type_analyzer.py +0 -151
  61. mcp_proxy_adapter/dispatchers/__init__.py +0 -1
  62. mcp_proxy_adapter/dispatchers/base_dispatcher.py +0 -85
  63. mcp_proxy_adapter/dispatchers/json_rpc_dispatcher.py +0 -235
  64. mcp_proxy_adapter/examples/analyze_config.py +0 -141
  65. mcp_proxy_adapter/examples/basic_integration.py +0 -155
  66. mcp_proxy_adapter/examples/docstring_and_schema_example.py +0 -69
  67. mcp_proxy_adapter/examples/extension_example.py +0 -72
  68. mcp_proxy_adapter/examples/help_best_practices.py +0 -67
  69. mcp_proxy_adapter/examples/help_usage.py +0 -64
  70. mcp_proxy_adapter/examples/mcp_proxy_client.py +0 -131
  71. mcp_proxy_adapter/examples/openapi_server.py +0 -383
  72. mcp_proxy_adapter/examples/project_structure_example.py +0 -47
  73. mcp_proxy_adapter/examples/testing_example.py +0 -64
  74. mcp_proxy_adapter/models.py +0 -47
  75. mcp_proxy_adapter/registry.py +0 -439
  76. mcp_proxy_adapter/schema.py +0 -257
  77. mcp_proxy_adapter/testing_utils.py +0 -112
  78. mcp_proxy_adapter/validators/__init__.py +0 -1
  79. mcp_proxy_adapter/validators/docstring_validator.py +0 -75
  80. mcp_proxy_adapter/validators/metadata_validator.py +0 -76
  81. mcp_proxy_adapter-2.1.16.dist-info/METADATA +0 -341
  82. mcp_proxy_adapter-2.1.16.dist-info/RECORD +0 -30
  83. {mcp_proxy_adapter-2.1.16.dist-info → mcp_proxy_adapter-3.0.0.dist-info}/WHEEL +0 -0
  84. {mcp_proxy_adapter-2.1.16.dist-info → mcp_proxy_adapter-3.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,439 +0,0 @@
1
- """
2
- Main CommandRegistry class for registering and managing commands.
3
- """
4
- import os
5
- import importlib
6
- import inspect
7
- import pkgutil
8
- import logging
9
- from typing import Dict, Any, Optional, List, Callable, Union, Type, Set, Tuple
10
- import docstring_parser
11
-
12
- from .dispatchers.base_dispatcher import BaseDispatcher
13
- from .dispatchers.json_rpc_dispatcher import JsonRpcDispatcher
14
- from .analyzers.type_analyzer import TypeAnalyzer
15
- from .analyzers.docstring_analyzer import DocstringAnalyzer
16
- from .validators.docstring_validator import DocstringValidator
17
- from .validators.metadata_validator import MetadataValidator
18
-
19
- logger = logging.getLogger("command_registry")
20
-
21
- class CommandRegistry:
22
- """
23
- Main class for registering and managing commands.
24
-
25
- CommandRegistry provides an interface for analyzing, validating, and registering
26
- commands based on their docstrings and type annotations. It also manages
27
- API generators and command dispatchers.
28
- """
29
-
30
- def __init__(
31
- self,
32
- dispatcher: Optional[BaseDispatcher] = None,
33
- strict: bool = True,
34
- auto_fix: bool = False
35
- ):
36
- """
37
- Initializes the command registry.
38
-
39
- Args:
40
- dispatcher: Command dispatcher for registering handlers
41
- strict: If True, stops registration when errors are detected
42
- auto_fix: If True, tries to automatically fix inconsistencies
43
- """
44
- # Use the specified dispatcher or create a default JSON-RPC dispatcher
45
- self.dispatcher = dispatcher or JsonRpcDispatcher()
46
-
47
- # Operation modes
48
- self.strict = strict
49
- self.auto_fix = auto_fix
50
-
51
- # Analyzers for extracting metadata
52
- self._analyzers = []
53
-
54
- # Validators for checking metadata consistency
55
- self._validators = []
56
-
57
- # API generators
58
- self._generators = []
59
-
60
- # Command information
61
- self._commands_info = {}
62
-
63
- # Paths for finding commands
64
- self._module_paths = []
65
-
66
- # Add standard analyzers
67
- self.add_analyzer(TypeAnalyzer())
68
- self.add_analyzer(DocstringAnalyzer())
69
-
70
- # Add standard validators
71
- self.add_validator(DocstringValidator())
72
- self.add_validator(MetadataValidator())
73
-
74
- def add_analyzer(self, analyzer) -> None:
75
- """
76
- Adds a metadata analyzer.
77
-
78
- Args:
79
- analyzer: Analyzer object with an analyze method
80
- """
81
- self._analyzers.append(analyzer)
82
-
83
- def add_validator(self, validator) -> None:
84
- """
85
- Adds a metadata validator.
86
-
87
- Args:
88
- validator: Validator object with a validate method
89
- """
90
- self._validators.append(validator)
91
-
92
- def add_generator(self, generator) -> None:
93
- """
94
- Adds an API generator.
95
-
96
- Args:
97
- generator: Generator object with set_dispatcher and generate_* methods
98
- """
99
- generator.set_dispatcher(self.dispatcher)
100
- self._generators.append(generator)
101
-
102
- def scan_modules(self, module_paths: List[str]) -> None:
103
- """
104
- Sets paths for searching modules with commands.
105
-
106
- Args:
107
- module_paths: List of module paths
108
- """
109
- self._module_paths = module_paths
110
-
111
- def find_command_handlers(self) -> Dict[str, Callable]:
112
- """
113
- Searches for command handler functions in the specified modules.
114
-
115
- Returns:
116
- Dict[str, Callable]: Dictionary {command_name: handler_function}
117
- """
118
- handlers = {}
119
-
120
- # If no search paths are specified, return an empty dictionary
121
- if not self._module_paths:
122
- return handlers
123
-
124
- # Search for handlers in each module
125
- for module_path in self._module_paths:
126
- try:
127
- module = importlib.import_module(module_path)
128
-
129
- # If this is a package, search in all its modules
130
- if hasattr(module, "__path__"):
131
- for _, name, is_pkg in pkgutil.iter_modules(module.__path__):
132
- # Full submodule name
133
- submodule_path = f"{module_path}.{name}"
134
-
135
- # Load the submodule
136
- try:
137
- submodule = importlib.import_module(submodule_path)
138
-
139
- # Search for handlers in the submodule
140
- for handler_name, handler in self._find_handlers_in_module(submodule):
141
- handlers[handler_name] = handler
142
- except ImportError as e:
143
- logger.warning(f"Failed to load submodule {submodule_path}: {str(e)}")
144
-
145
- # Search for handlers in the module itself
146
- for handler_name, handler in self._find_handlers_in_module(module):
147
- handlers[handler_name] = handler
148
- except ImportError as e:
149
- logger.warning(f"Failed to load module {module_path}: {str(e)}")
150
-
151
- return handlers
152
-
153
- def _find_handlers_in_module(self, module) -> List[Tuple[str, Callable]]:
154
- """
155
- Searches for command handler functions in a module.
156
-
157
- Args:
158
- module: Loaded module
159
-
160
- Returns:
161
- List[Tuple[str, Callable]]: List of pairs (command_name, handler_function)
162
- """
163
- result = []
164
-
165
- # Get all module attributes
166
- for name in dir(module):
167
- # Skip private attributes
168
- if name.startswith("_"):
169
- continue
170
-
171
- # Get the attribute
172
- attr = getattr(module, name)
173
-
174
- # Check that it's a function or method
175
- if callable(attr) and (inspect.isfunction(attr) or inspect.ismethod(attr)):
176
- # Check if the function is a command handler
177
- command_name = self._get_command_name_from_handler(attr, name)
178
-
179
- if command_name:
180
- result.append((command_name, attr))
181
-
182
- return result
183
-
184
- def _get_command_name_from_handler(self, handler: Callable, handler_name: str) -> Optional[str]:
185
- """
186
- Determines the command name based on the function name or decorator.
187
-
188
- Args:
189
- handler: Handler function
190
- handler_name: Function name
191
-
192
- Returns:
193
- Optional[str]: Command name or None if the function is not a handler
194
- """
195
- # Check if the function has a command_name attribute (set by a decorator)
196
- if hasattr(handler, "command_name"):
197
- return handler.command_name
198
-
199
- # Check handler name patterns
200
- if handler_name.endswith("_command"):
201
- # Command name without the _command suffix
202
- return handler_name[:-8]
203
-
204
- if handler_name.startswith("execute_"):
205
- # Command name without the execute_ prefix
206
- return handler_name[8:]
207
-
208
- if handler_name == "execute":
209
- # The execute handler can process any command
210
- # In this case, the command name is determined by the module name
211
- module_name = handler.__module__.split(".")[-1]
212
- if module_name != "execute":
213
- return module_name
214
-
215
- # Check if the function has a docstring
216
- if handler.__doc__:
217
- try:
218
- # Parse the docstring
219
- parsed_doc = docstring_parser.parse(handler.__doc__)
220
-
221
- # Check if the docstring explicitly specifies a command name
222
- for meta in parsed_doc.meta:
223
- if meta.type_name == "command":
224
- return meta.description
225
- except Exception:
226
- pass
227
-
228
- # By default, use the function name as the command name
229
- return handler_name
230
-
231
- def analyze_handler(self, command_name: str, handler: Callable) -> Dict[str, Any]:
232
- """
233
- Analyzes the handler function and extracts metadata.
234
-
235
- Args:
236
- command_name: Command name
237
- handler: Handler function
238
-
239
- Returns:
240
- Dict[str, Any]: Command metadata
241
- """
242
- # Base metadata
243
- metadata = {
244
- "description": "",
245
- "summary": "",
246
- "parameters": {}
247
- }
248
-
249
- # Apply all analyzers
250
- for analyzer in self._analyzers:
251
- try:
252
- # Get metadata from the analyzer
253
- analyzer_metadata = analyzer.analyze(handler)
254
-
255
- # Merge metadata
256
- for key, value in analyzer_metadata.items():
257
- if key == "parameters" and metadata.get(key):
258
- # Merge parameter information
259
- for param_name, param_info in value.items():
260
- if param_name in metadata[key]:
261
- # Update existing parameter
262
- metadata[key][param_name].update(param_info)
263
- else:
264
- # Add new parameter
265
- metadata[key][param_name] = param_info
266
- else:
267
- # For other keys, simply replace the value
268
- metadata[key] = value
269
- except Exception as e:
270
- logger.warning(f"Error analyzing command '{command_name}' with analyzer {analyzer.__class__.__name__}: {str(e)}")
271
-
272
- # In strict mode, propagate the exception
273
- if self.strict:
274
- raise
275
-
276
- return metadata
277
-
278
- def validate_handler(self, command_name: str, handler: Callable, metadata: Dict[str, Any]) -> Tuple[bool, List[str]]:
279
- """
280
- Validates the correspondence between the handler function and metadata.
281
-
282
- Args:
283
- command_name: Command name
284
- handler: Handler function
285
- metadata: Command metadata
286
-
287
- Returns:
288
- Tuple[bool, List[str]]: Validity flag and list of errors
289
- """
290
- errors = []
291
-
292
- # Apply all validators
293
- for validator in self._validators:
294
- try:
295
- # Check validity using the validator
296
- is_valid, validator_errors = validator.validate(handler, command_name, metadata)
297
-
298
- # Add errors to the general list
299
- errors.extend(validator_errors)
300
- except Exception as e:
301
- logger.warning(f"Error validating command '{command_name}' with validator {validator.__class__.__name__}: {str(e)}")
302
- errors.append(f"Validation error: {str(e)}")
303
-
304
- # In strict mode, propagate the exception
305
- if self.strict:
306
- raise
307
-
308
- return len(errors) == 0, errors
309
-
310
- def register_command(self, command_name: str, handler: Callable) -> bool:
311
- """
312
- Registers a command based on a handler function.
313
-
314
- Args:
315
- command_name: Command name
316
- handler: Handler function
317
-
318
- Returns:
319
- bool: True if the command was registered successfully, False otherwise
320
- """
321
- try:
322
- # Analyze the handler
323
- metadata = self.analyze_handler(command_name, handler)
324
-
325
- # Validate metadata
326
- is_valid, errors = self.validate_handler(command_name, handler, metadata)
327
-
328
- # Output errors
329
- if not is_valid:
330
- logger.warning(f"Errors in command '{command_name}':")
331
- for error in errors:
332
- logger.warning(f" - {error}")
333
-
334
- # In strict mode without auto-fix, skip registration
335
- if self.strict and not self.auto_fix:
336
- logger.error(f"Command registration '{command_name}' skipped due to errors")
337
- return False
338
-
339
- # Register the command in the dispatcher
340
- self.dispatcher.register_handler(
341
- command=command_name,
342
- handler=handler,
343
- description=metadata.get("description", ""),
344
- summary=metadata.get("summary", ""),
345
- params=metadata.get("parameters", {})
346
- )
347
-
348
- # Save command information
349
- self._commands_info[command_name] = {
350
- "metadata": metadata,
351
- "handler": handler
352
- }
353
-
354
- logger.info(f"Registered command '{command_name}'")
355
- return True
356
- except Exception as e:
357
- logger.error(f"Error registering command '{command_name}': {str(e)}")
358
-
359
- # In strict mode, propagate the exception
360
- if self.strict:
361
- raise
362
-
363
- return False
364
-
365
- def register_all_commands(self) -> Dict[str, Any]:
366
- """
367
- Registers all found commands.
368
-
369
- Returns:
370
- Dict[str, Any]: Registration statistics
371
- """
372
- # Counters
373
- stats = {
374
- "successful": 0,
375
- "failed": 0,
376
- "skipped": 0,
377
- "total": 0
378
- }
379
-
380
- # Search for command handlers
381
- handlers = self.find_command_handlers()
382
-
383
- # Register each command
384
- for command_name, handler in handlers.items():
385
- # Skip help, it's already registered in the dispatcher
386
- if command_name == "help":
387
- stats["skipped"] += 1
388
- continue
389
-
390
- if self.register_command(command_name, handler):
391
- stats["successful"] += 1
392
- else:
393
- stats["failed"] += 1
394
-
395
- stats["total"] += 1
396
-
397
- # Generate API interfaces
398
- for generator in self._generators:
399
- if hasattr(generator, "generate_endpoints"):
400
- generator.generate_endpoints()
401
-
402
- if hasattr(generator, "generate_schema"):
403
- generator.generate_schema()
404
-
405
- # Output statistics
406
- logger.info(f"Command registration results:")
407
- logger.info(f" - Successful: {stats['successful']}")
408
- logger.info(f" - With errors: {stats['failed']}")
409
- logger.info(f" - Skipped: {stats['skipped']}")
410
- logger.info(f" - Total in dispatcher: {len(self.dispatcher.get_valid_commands())}")
411
-
412
- if stats["failed"] > 0 and self.strict:
413
- logger.warning("WARNING: Some commands were not registered due to errors")
414
- logger.warning("Use strict=False to register all commands")
415
- logger.warning("Or auto_fix=True to automatically fix errors")
416
-
417
- return stats
418
-
419
- def execute(self, command: str, **kwargs) -> Any:
420
- """
421
- Executes a command through the dispatcher.
422
-
423
- Args:
424
- command: Command name
425
- **kwargs: Command parameters
426
-
427
- Returns:
428
- Any: Command execution result
429
- """
430
- return self.dispatcher.execute(command, **kwargs)
431
-
432
- def get_commands_info(self) -> Dict[str, Dict[str, Any]]:
433
- """
434
- Returns information about all registered commands.
435
-
436
- Returns:
437
- Dict[str, Dict[str, Any]]: Dictionary {command_name: information}
438
- """
439
- return self.dispatcher.get_commands_info()
@@ -1,257 +0,0 @@
1
- """
2
- Module for optimizing OpenAPI schema for MCP Proxy.
3
- """
4
- from typing import Dict, Any, List, Optional
5
-
6
- class SchemaOptimizer:
7
- """
8
- OpenAPI schema optimizer for use with MCP Proxy.
9
-
10
- This class transforms a standard OpenAPI schema into a format
11
- more suitable for use with MCP Proxy and AI models.
12
- """
13
-
14
- def optimize(
15
- self,
16
- schema: Dict[str, Any],
17
- cmd_endpoint: str,
18
- commands_info: Dict[str, Dict[str, Any]]
19
- ) -> Dict[str, Any]:
20
- """
21
- Optimizes OpenAPI schema for MCP Proxy.
22
-
23
- Args:
24
- schema: Original OpenAPI schema
25
- cmd_endpoint: Path for universal JSON-RPC endpoint
26
- commands_info: Information about registered commands
27
-
28
- Returns:
29
- Dict[str, Any]: Optimized schema
30
- """
31
- # Create a new schema in a format compatible with the proxy
32
- optimized = {
33
- "openapi": "3.0.2",
34
- "info": {
35
- "title": "Command Registry API",
36
- "description": "API for executing commands through MCPProxy",
37
- "version": "1.0.0"
38
- },
39
- "paths": {
40
- cmd_endpoint: {
41
- "post": {
42
- "summary": "Execute command",
43
- "description": "Universal endpoint for executing various commands",
44
- "operationId": "execute_command",
45
- "requestBody": {
46
- "content": {
47
- "application/json": {
48
- "schema": {
49
- "$ref": "#/components/schemas/CommandRequest"
50
- },
51
- "examples": {}
52
- }
53
- },
54
- "required": True
55
- },
56
- "responses": {
57
- "200": {
58
- "description": "Command executed successfully",
59
- "content": {
60
- "application/json": {
61
- "schema": {
62
- "$ref": "#/components/schemas/CommandResponse"
63
- }
64
- }
65
- }
66
- },
67
- "400": {
68
- "description": "Error in request or during command execution",
69
- "content": {
70
- "application/json": {
71
- "schema": {
72
- "type": "object",
73
- "properties": {
74
- "detail": {
75
- "type": "string",
76
- "description": "Error description"
77
- }
78
- }
79
- }
80
- }
81
- }
82
- }
83
- }
84
- }
85
- }
86
- },
87
- "components": {
88
- "schemas": {
89
- "CommandRequest": {
90
- "title": "CommandRequest",
91
- "description": "Command execution request",
92
- "type": "object",
93
- "required": ["command"],
94
- "properties": {
95
- "command": {
96
- "title": "Command",
97
- "description": "Command to execute",
98
- "type": "string",
99
- "enum": list(commands_info.keys())
100
- },
101
- "params": {
102
- "title": "Parameters",
103
- "description": "Command parameters, depend on command type",
104
- "oneOf": []
105
- }
106
- }
107
- },
108
- "CommandResponse": {
109
- "title": "CommandResponse",
110
- "description": "Command execution response",
111
- "type": "object",
112
- "required": ["result"],
113
- "properties": {
114
- "result": {
115
- "title": "Result",
116
- "description": "Command execution result"
117
- }
118
- }
119
- }
120
- },
121
- "examples": {}
122
- }
123
- }
124
-
125
- # Add parameter schemas and examples for each command
126
- for cmd_name, cmd_info in commands_info.items():
127
- param_schema_name = f"{cmd_name.capitalize()}Params"
128
- params = cmd_info.get("params", {})
129
- has_params = bool(params)
130
-
131
- # Define parameter schema (даже если params пустой, схема будет пустым объектом)
132
- param_schema = {
133
- "title": param_schema_name,
134
- "description": f"Parameters for command {cmd_name}",
135
- "type": "object",
136
- "properties": {},
137
- }
138
- required_params = []
139
- example_params = {}
140
-
141
- for param_name, param_info in params.items():
142
- param_property = {
143
- "title": param_name.capitalize(),
144
- "type": param_info.get("type", "string"),
145
- "description": param_info.get("description", "")
146
- }
147
- if "default" in param_info:
148
- param_property["default"] = param_info["default"]
149
- if "enum" in param_info:
150
- param_property["enum"] = param_info["enum"]
151
- param_schema["properties"][param_name] = param_property
152
- if param_info.get("required", False):
153
- required_params.append(param_name)
154
- if "example" in param_info:
155
- example_params[param_name] = param_info["example"]
156
- elif "default" in param_info:
157
- example_params[param_name] = param_info["default"]
158
- elif param_info.get("type") == "string":
159
- example_params[param_name] = "example_value"
160
- elif param_info.get("type") == "integer":
161
- example_params[param_name] = 1
162
- elif param_info.get("type") == "boolean":
163
- example_params[param_name] = False
164
-
165
- if required_params:
166
- param_schema["required"] = required_params
167
-
168
- # Добавляем схему параметров всегда, даже если она пустая
169
- optimized["components"]["schemas"][param_schema_name] = param_schema
170
-
171
- # Добавляем $ref на схему параметров в oneOf всегда
172
- optimized["components"]["schemas"]["CommandRequest"]["properties"]["params"]["oneOf"].append({
173
- "$ref": f"#/components/schemas/{param_schema_name}"
174
- })
175
-
176
- # Пример использования команды
177
- example_id = f"{cmd_name}_example"
178
- example = {
179
- "summary": f"Example of using command {cmd_name}",
180
- "value": {
181
- "command": cmd_name
182
- }
183
- }
184
- if has_params:
185
- example["value"]["params"] = example_params
186
- optimized["components"]["examples"][example_id] = example
187
- optimized["paths"][cmd_endpoint]["post"]["requestBody"]["content"]["application/json"]["examples"][example_id] = {
188
- "$ref": f"#/components/examples/{example_id}"
189
- }
190
-
191
- # Для команд без параметров добавляем type: null в oneOf
192
- optimized_oneof = optimized["components"]["schemas"]["CommandRequest"]["properties"]["params"]["oneOf"]
193
- optimized_oneof.append({"type": "null"})
194
-
195
- # Add tool descriptions to schema for AI models
196
- self._add_tool_descriptions(optimized, commands_info)
197
- return optimized
198
-
199
- def _add_tool_descriptions(
200
- self,
201
- schema: Dict[str, Any],
202
- commands_info: Dict[str, Dict[str, Any]]
203
- ) -> None:
204
- """
205
- Adds AI tool descriptions to the schema.
206
-
207
- This method enhances the OpenAPI schema with special descriptions
208
- for better integration with AI models and MCPProxy.
209
-
210
- Args:
211
- schema: OpenAPI schema to enhance
212
- commands_info: Information about registered commands
213
- """
214
- # Add AI tool descriptions to x-mcp-tools
215
- schema["x-mcp-tools"] = []
216
-
217
- for cmd_name, cmd_info in commands_info.items():
218
- # Create tool description
219
- tool = {
220
- "name": f"mcp_{cmd_name}", # Add mcp_ prefix to command name
221
- "description": cmd_info.get("description", "") or cmd_info.get("summary", ""),
222
- "parameters": {
223
- "type": "object",
224
- "properties": {},
225
- "required": []
226
- }
227
- }
228
-
229
- # Add parameters
230
- for param_name, param_info in cmd_info.get("params", {}).items():
231
- # Convert parameter to JSON Schema format
232
- param_schema = {}
233
-
234
- # Parameter type
235
- param_schema["type"] = param_info.get("type", "string")
236
-
237
- # Description
238
- if "description" in param_info:
239
- param_schema["description"] = param_info["description"]
240
-
241
- # Default value
242
- if "default" in param_info:
243
- param_schema["default"] = param_info["default"]
244
-
245
- # Possible values
246
- if "enum" in param_info:
247
- param_schema["enum"] = param_info["enum"]
248
-
249
- # Add parameter to schema
250
- tool["parameters"]["properties"][param_name] = param_schema
251
-
252
- # If parameter is required, add to required list
253
- if param_info.get("required", False):
254
- tool["parameters"]["required"].append(param_name)
255
-
256
- # Add tool to list
257
- schema["x-mcp-tools"].append(tool)