mcp-proxy-adapter 2.1.17__py3-none-any.whl → 3.0.1__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 (135) 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 +245 -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 +25 -0
  14. examples/basic_example/docs/EN/README.md +177 -0
  15. examples/basic_example/docs/RU/README.md +177 -0
  16. examples/basic_example/server.py +151 -0
  17. examples/basic_example/tests/conftest.py +243 -0
  18. examples/commands/echo_command.py +52 -0
  19. examples/commands/echo_result.py +65 -0
  20. examples/commands/get_date_command.py +98 -0
  21. examples/commands/new_uuid4_command.py +91 -0
  22. examples/complete_example/Dockerfile +24 -0
  23. examples/complete_example/README.md +92 -0
  24. examples/complete_example/__init__.py +8 -0
  25. examples/complete_example/commands/__init__.py +5 -0
  26. examples/complete_example/commands/system_command.py +328 -0
  27. examples/complete_example/config.json +41 -0
  28. examples/complete_example/configs/config.dev.yaml +40 -0
  29. examples/complete_example/configs/config.docker.yaml +40 -0
  30. examples/complete_example/docker-compose.yml +35 -0
  31. examples/complete_example/requirements.txt +20 -0
  32. examples/complete_example/server.py +139 -0
  33. examples/minimal_example/README.md +65 -0
  34. examples/minimal_example/__init__.py +8 -0
  35. examples/minimal_example/config.json +14 -0
  36. examples/minimal_example/main.py +136 -0
  37. examples/minimal_example/simple_server.py +163 -0
  38. examples/minimal_example/tests/conftest.py +171 -0
  39. examples/minimal_example/tests/test_hello_command.py +111 -0
  40. examples/minimal_example/tests/test_integration.py +181 -0
  41. examples/server.py +69 -0
  42. examples/simple_server.py +128 -0
  43. examples/test_server.py +134 -0
  44. examples/tool_description_example.py +82 -0
  45. mcp_proxy_adapter/__init__.py +33 -1
  46. mcp_proxy_adapter/api/__init__.py +0 -0
  47. mcp_proxy_adapter/api/app.py +391 -0
  48. mcp_proxy_adapter/api/handlers.py +229 -0
  49. mcp_proxy_adapter/api/middleware/__init__.py +49 -0
  50. mcp_proxy_adapter/api/middleware/auth.py +146 -0
  51. mcp_proxy_adapter/api/middleware/base.py +79 -0
  52. mcp_proxy_adapter/api/middleware/error_handling.py +198 -0
  53. mcp_proxy_adapter/api/middleware/logging.py +96 -0
  54. mcp_proxy_adapter/api/middleware/performance.py +83 -0
  55. mcp_proxy_adapter/api/middleware/rate_limit.py +152 -0
  56. mcp_proxy_adapter/api/schemas.py +305 -0
  57. mcp_proxy_adapter/api/tool_integration.py +223 -0
  58. mcp_proxy_adapter/api/tools.py +198 -0
  59. mcp_proxy_adapter/commands/__init__.py +19 -0
  60. mcp_proxy_adapter/commands/base.py +301 -0
  61. mcp_proxy_adapter/commands/command_registry.py +231 -0
  62. mcp_proxy_adapter/commands/config_command.py +113 -0
  63. mcp_proxy_adapter/commands/health_command.py +136 -0
  64. mcp_proxy_adapter/commands/help_command.py +193 -0
  65. mcp_proxy_adapter/commands/result.py +215 -0
  66. mcp_proxy_adapter/config.py +195 -0
  67. mcp_proxy_adapter/core/__init__.py +0 -0
  68. mcp_proxy_adapter/core/errors.py +173 -0
  69. mcp_proxy_adapter/core/logging.py +205 -0
  70. mcp_proxy_adapter/core/utils.py +138 -0
  71. mcp_proxy_adapter/custom_openapi.py +125 -0
  72. mcp_proxy_adapter/openapi.py +403 -0
  73. mcp_proxy_adapter/py.typed +0 -0
  74. mcp_proxy_adapter/schemas/base_schema.json +114 -0
  75. mcp_proxy_adapter/schemas/openapi_schema.json +314 -0
  76. mcp_proxy_adapter/tests/__init__.py +0 -0
  77. mcp_proxy_adapter/tests/api/__init__.py +3 -0
  78. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +115 -0
  79. mcp_proxy_adapter/tests/api/test_middleware.py +336 -0
  80. mcp_proxy_adapter/tests/commands/__init__.py +3 -0
  81. mcp_proxy_adapter/tests/commands/test_config_command.py +211 -0
  82. mcp_proxy_adapter/tests/commands/test_echo_command.py +127 -0
  83. mcp_proxy_adapter/tests/commands/test_help_command.py +133 -0
  84. mcp_proxy_adapter/tests/conftest.py +131 -0
  85. mcp_proxy_adapter/tests/functional/__init__.py +3 -0
  86. mcp_proxy_adapter/tests/functional/test_api.py +235 -0
  87. mcp_proxy_adapter/tests/integration/__init__.py +3 -0
  88. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +130 -0
  89. mcp_proxy_adapter/tests/integration/test_integration.py +255 -0
  90. mcp_proxy_adapter/tests/performance/__init__.py +3 -0
  91. mcp_proxy_adapter/tests/performance/test_performance.py +189 -0
  92. mcp_proxy_adapter/tests/stubs/__init__.py +10 -0
  93. mcp_proxy_adapter/tests/stubs/echo_command.py +104 -0
  94. mcp_proxy_adapter/tests/test_api_endpoints.py +271 -0
  95. mcp_proxy_adapter/tests/test_api_handlers.py +289 -0
  96. mcp_proxy_adapter/tests/test_base_command.py +123 -0
  97. mcp_proxy_adapter/tests/test_batch_requests.py +117 -0
  98. mcp_proxy_adapter/tests/test_command_registry.py +245 -0
  99. mcp_proxy_adapter/tests/test_config.py +127 -0
  100. mcp_proxy_adapter/tests/test_utils.py +65 -0
  101. mcp_proxy_adapter/tests/unit/__init__.py +3 -0
  102. mcp_proxy_adapter/tests/unit/test_base_command.py +130 -0
  103. mcp_proxy_adapter/tests/unit/test_config.py +217 -0
  104. mcp_proxy_adapter/version.py +3 -0
  105. mcp_proxy_adapter-3.0.1.dist-info/METADATA +200 -0
  106. mcp_proxy_adapter-3.0.1.dist-info/RECORD +109 -0
  107. {mcp_proxy_adapter-2.1.17.dist-info → mcp_proxy_adapter-3.0.1.dist-info}/top_level.txt +1 -0
  108. mcp_proxy_adapter/adapter.py +0 -697
  109. mcp_proxy_adapter/analyzers/__init__.py +0 -1
  110. mcp_proxy_adapter/analyzers/docstring_analyzer.py +0 -199
  111. mcp_proxy_adapter/analyzers/type_analyzer.py +0 -151
  112. mcp_proxy_adapter/dispatchers/__init__.py +0 -1
  113. mcp_proxy_adapter/dispatchers/base_dispatcher.py +0 -85
  114. mcp_proxy_adapter/dispatchers/json_rpc_dispatcher.py +0 -262
  115. mcp_proxy_adapter/examples/analyze_config.py +0 -141
  116. mcp_proxy_adapter/examples/basic_integration.py +0 -155
  117. mcp_proxy_adapter/examples/docstring_and_schema_example.py +0 -69
  118. mcp_proxy_adapter/examples/extension_example.py +0 -72
  119. mcp_proxy_adapter/examples/help_best_practices.py +0 -67
  120. mcp_proxy_adapter/examples/help_usage.py +0 -64
  121. mcp_proxy_adapter/examples/mcp_proxy_client.py +0 -131
  122. mcp_proxy_adapter/examples/openapi_server.py +0 -383
  123. mcp_proxy_adapter/examples/project_structure_example.py +0 -47
  124. mcp_proxy_adapter/examples/testing_example.py +0 -64
  125. mcp_proxy_adapter/models.py +0 -47
  126. mcp_proxy_adapter/registry.py +0 -439
  127. mcp_proxy_adapter/schema.py +0 -257
  128. mcp_proxy_adapter/testing_utils.py +0 -112
  129. mcp_proxy_adapter/validators/__init__.py +0 -1
  130. mcp_proxy_adapter/validators/docstring_validator.py +0 -75
  131. mcp_proxy_adapter/validators/metadata_validator.py +0 -76
  132. mcp_proxy_adapter-2.1.17.dist-info/METADATA +0 -376
  133. mcp_proxy_adapter-2.1.17.dist-info/RECORD +0 -30
  134. {mcp_proxy_adapter-2.1.17.dist-info → mcp_proxy_adapter-3.0.1.dist-info}/WHEEL +0 -0
  135. {mcp_proxy_adapter-2.1.17.dist-info → mcp_proxy_adapter-3.0.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,205 @@
1
+ """
2
+ Module for configuring logging in the microservice.
3
+ """
4
+
5
+ import logging
6
+ import os
7
+ import sys
8
+ import uuid
9
+ from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
10
+ from typing import Dict, Optional, Any
11
+
12
+ from mcp_proxy_adapter.config import config
13
+
14
+
15
+ class CustomFormatter(logging.Formatter):
16
+ """
17
+ Custom formatter for logs with colored output in console.
18
+ """
19
+ grey = "\x1b[38;20m"
20
+ yellow = "\x1b[33;20m"
21
+ red = "\x1b[31;20m"
22
+ bold_red = "\x1b[31;1m"
23
+ reset = "\x1b[0m"
24
+ format_str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
25
+
26
+ FORMATS = {
27
+ logging.DEBUG: grey + format_str + reset,
28
+ logging.INFO: grey + format_str + reset,
29
+ logging.WARNING: yellow + format_str + reset,
30
+ logging.ERROR: red + format_str + reset,
31
+ logging.CRITICAL: bold_red + format_str + reset
32
+ }
33
+
34
+ def format(self, record):
35
+ log_fmt = self.FORMATS.get(record.levelno)
36
+ formatter = logging.Formatter(log_fmt)
37
+ return formatter.format(record)
38
+
39
+
40
+ class RequestContextFilter(logging.Filter):
41
+ """
42
+ Filter for adding request context to logs.
43
+ """
44
+
45
+ def __init__(self, request_id: Optional[str] = None):
46
+ super().__init__()
47
+ self.request_id = request_id
48
+
49
+ def filter(self, record):
50
+ # Add request_id attribute to the record
51
+ record.request_id = self.request_id or "no-request-id"
52
+ return True
53
+
54
+
55
+ class RequestLogger:
56
+ """
57
+ Logger class for logging requests with context.
58
+ """
59
+
60
+ def __init__(self, logger_name: str, request_id: Optional[str] = None):
61
+ """
62
+ Initialize request logger.
63
+
64
+ Args:
65
+ logger_name: Logger name.
66
+ request_id: Request identifier.
67
+ """
68
+ self.logger = logging.getLogger(logger_name)
69
+ self.request_id = request_id or str(uuid.uuid4())
70
+ self.filter = RequestContextFilter(self.request_id)
71
+ self.logger.addFilter(self.filter)
72
+
73
+ def debug(self, msg: str, *args, **kwargs):
74
+ """Log message with DEBUG level."""
75
+ self.logger.debug(f"[{self.request_id}] {msg}", *args, **kwargs)
76
+
77
+ def info(self, msg: str, *args, **kwargs):
78
+ """Log message with INFO level."""
79
+ self.logger.info(f"[{self.request_id}] {msg}", *args, **kwargs)
80
+
81
+ def warning(self, msg: str, *args, **kwargs):
82
+ """Log message with WARNING level."""
83
+ self.logger.warning(f"[{self.request_id}] {msg}", *args, **kwargs)
84
+
85
+ def error(self, msg: str, *args, **kwargs):
86
+ """Log message with ERROR level."""
87
+ self.logger.error(f"[{self.request_id}] {msg}", *args, **kwargs)
88
+
89
+ def exception(self, msg: str, *args, **kwargs):
90
+ """Log exception with traceback."""
91
+ self.logger.exception(f"[{self.request_id}] {msg}", *args, **kwargs)
92
+
93
+ def critical(self, msg: str, *args, **kwargs):
94
+ """Log message with CRITICAL level."""
95
+ self.logger.critical(f"[{self.request_id}] {msg}", *args, **kwargs)
96
+
97
+
98
+ def setup_logging(
99
+ level: Optional[str] = None,
100
+ log_file: Optional[str] = None,
101
+ max_bytes: Optional[int] = None,
102
+ backup_count: Optional[int] = None,
103
+ rotation_type: Optional[str] = None,
104
+ rotation_when: Optional[str] = None,
105
+ rotation_interval: Optional[int] = None
106
+ ) -> logging.Logger:
107
+ """
108
+ Configure logging for the microservice.
109
+
110
+ Args:
111
+ level: Logging level. By default, taken from configuration.
112
+ log_file: Path to log file. By default, taken from configuration.
113
+ max_bytes: Maximum log file size in bytes. By default, taken from configuration.
114
+ backup_count: Number of rotation files. By default, taken from configuration.
115
+ rotation_type: Type of log rotation ('size' or 'time'). By default, taken from configuration.
116
+ rotation_when: Time unit for rotation (D, H, M, S). By default, taken from configuration.
117
+ rotation_interval: Interval for rotation. By default, taken from configuration.
118
+
119
+ Returns:
120
+ Configured logger.
121
+ """
122
+ # Get parameters from configuration if not explicitly specified
123
+ level = level or config.get("logging.level", "INFO")
124
+ log_file = log_file or config.get("logging.file")
125
+ rotation_type = rotation_type or config.get("logging.rotation.type", "size")
126
+
127
+ # Size-based rotation parameters
128
+ max_bytes = max_bytes or config.get("logging.rotation.max_bytes", 10 * 1024 * 1024) # 10 MB by default
129
+ backup_count = backup_count or config.get("logging.rotation.backup_count", 5)
130
+
131
+ # Time-based rotation parameters
132
+ rotation_when = rotation_when or config.get("logging.rotation.when", "D") # Daily by default
133
+ rotation_interval = rotation_interval or config.get("logging.rotation.interval", 1)
134
+
135
+ # Convert string logging level to constant
136
+ numeric_level = getattr(logging, level.upper(), None)
137
+ if not isinstance(numeric_level, int):
138
+ numeric_level = logging.INFO
139
+
140
+ # Create root logger
141
+ logger = logging.getLogger("mcp_proxy_adapter")
142
+ logger.setLevel(numeric_level)
143
+ logger.handlers = [] # Clear handlers in case of repeated call
144
+
145
+ # Create console handler
146
+ console_handler = logging.StreamHandler(sys.stdout)
147
+ console_handler.setLevel(numeric_level)
148
+ console_handler.setFormatter(CustomFormatter())
149
+ logger.addHandler(console_handler)
150
+
151
+ # Create file handler if file specified
152
+ if log_file:
153
+ # Create directory for log file if it doesn't exist
154
+ log_dir = os.path.dirname(log_file)
155
+ if log_dir and not os.path.exists(log_dir):
156
+ os.makedirs(log_dir, exist_ok=True)
157
+
158
+ # Choose rotation type
159
+ if rotation_type.lower() == "time":
160
+ file_handler = TimedRotatingFileHandler(
161
+ log_file,
162
+ when=rotation_when,
163
+ interval=rotation_interval,
164
+ backupCount=backup_count,
165
+ encoding="utf-8"
166
+ )
167
+ else: # Default to size-based rotation
168
+ file_handler = RotatingFileHandler(
169
+ log_file,
170
+ maxBytes=max_bytes,
171
+ backupCount=backup_count,
172
+ encoding="utf-8"
173
+ )
174
+
175
+ file_handler.setLevel(numeric_level)
176
+ file_formatter = logging.Formatter(
177
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
178
+ )
179
+ file_handler.setFormatter(file_formatter)
180
+ logger.addHandler(file_handler)
181
+
182
+ # Configure loggers for external libraries
183
+ log_levels = config.get("logging.levels", {})
184
+ for logger_name, logger_level in log_levels.items():
185
+ lib_logger = logging.getLogger(logger_name)
186
+ lib_logger.setLevel(getattr(logging, logger_level.upper(), logging.INFO))
187
+
188
+ return logger
189
+
190
+
191
+ def get_logger(name: str) -> logging.Logger:
192
+ """
193
+ Get a logger with the specified name.
194
+
195
+ Args:
196
+ name: Logger name.
197
+
198
+ Returns:
199
+ Configured logger instance.
200
+ """
201
+ return logging.getLogger(name)
202
+
203
+
204
+ # Global logger for use throughout the application
205
+ logger = setup_logging()
@@ -0,0 +1,138 @@
1
+ """
2
+ Module with utility functions for the microservice.
3
+ """
4
+
5
+ import hashlib
6
+ import json
7
+ import os
8
+ import sys
9
+ import time
10
+ import uuid
11
+ from datetime import datetime
12
+ from typing import Any, Dict, List, Optional, Tuple, Union
13
+
14
+ from mcp_proxy_adapter.core.logging import logger
15
+
16
+
17
+ def generate_id() -> str:
18
+ """
19
+ Generates a unique identifier.
20
+
21
+ Returns:
22
+ String with unique identifier.
23
+ """
24
+ return str(uuid.uuid4())
25
+
26
+
27
+ def get_timestamp() -> int:
28
+ """
29
+ Returns current timestamp in milliseconds.
30
+
31
+ Returns:
32
+ Integer - timestamp in milliseconds.
33
+ """
34
+ return int(time.time() * 1000)
35
+
36
+
37
+ def format_datetime(dt: Optional[datetime] = None, format_str: str = "%Y-%m-%dT%H:%M:%S.%fZ") -> str:
38
+ """
39
+ Formats date and time as string.
40
+
41
+ Args:
42
+ dt: Datetime object to format. If None, current time is used.
43
+ format_str: Format string for output.
44
+
45
+ Returns:
46
+ Formatted date/time string.
47
+ """
48
+ dt = dt or datetime.utcnow()
49
+ return dt.strftime(format_str)
50
+
51
+
52
+ def parse_datetime(dt_str: str, format_str: str = "%Y-%m-%dT%H:%M:%S.%fZ") -> datetime:
53
+ """
54
+ Parses date/time string into datetime object.
55
+
56
+ Args:
57
+ dt_str: Date/time string.
58
+ format_str: Date/time string format.
59
+
60
+ Returns:
61
+ Datetime object.
62
+ """
63
+ return datetime.strptime(dt_str, format_str)
64
+
65
+
66
+ def safe_json_loads(s: str, default: Any = None) -> Any:
67
+ """
68
+ Safe JSON string loading.
69
+
70
+ Args:
71
+ s: JSON string to load.
72
+ default: Default value on parsing error.
73
+
74
+ Returns:
75
+ Loaded object or default value on error.
76
+ """
77
+ try:
78
+ return json.loads(s)
79
+ except Exception as e:
80
+ logger.error(f"Error parsing JSON: {e}")
81
+ return default
82
+
83
+
84
+ def safe_json_dumps(obj: Any, default: str = "{}", indent: Optional[int] = None) -> str:
85
+ """
86
+ Safe object conversion to JSON string.
87
+
88
+ Args:
89
+ obj: Object to convert.
90
+ default: Default string on serialization error.
91
+ indent: Indentation for JSON formatting.
92
+
93
+ Returns:
94
+ JSON string or default string on error.
95
+ """
96
+ try:
97
+ return json.dumps(obj, ensure_ascii=False, indent=indent)
98
+ except Exception as e:
99
+ logger.error(f"Error serializing to JSON: {e}")
100
+ return default
101
+
102
+
103
+ def calculate_hash(data: Union[str, bytes], algorithm: str = "sha256") -> str:
104
+ """
105
+ Calculates hash for data.
106
+
107
+ Args:
108
+ data: Data to hash (string or bytes).
109
+ algorithm: Hashing algorithm (md5, sha1, sha256, etc.).
110
+
111
+ Returns:
112
+ String with hash in hexadecimal format.
113
+ """
114
+ if isinstance(data, str):
115
+ data = data.encode("utf-8")
116
+
117
+ hash_obj = hashlib.new(algorithm)
118
+ hash_obj.update(data)
119
+ return hash_obj.hexdigest()
120
+
121
+
122
+ def ensure_directory(path: str) -> bool:
123
+ """
124
+ Checks directory existence and creates it if necessary.
125
+
126
+ Args:
127
+ path: Path to directory.
128
+
129
+ Returns:
130
+ True if directory exists or was successfully created, otherwise False.
131
+ """
132
+ try:
133
+ if not os.path.exists(path):
134
+ os.makedirs(path, exist_ok=True)
135
+ return True
136
+ except Exception as e:
137
+ logger.error(f"Error creating directory {path}: {e}")
138
+ return False
@@ -0,0 +1,125 @@
1
+ """
2
+ Custom OpenAPI schema generator for MCP Microservice compatible with MCP-Proxy.
3
+ """
4
+ import json
5
+ from copy import deepcopy
6
+ from pathlib import Path
7
+ from typing import Any, Dict, List, Optional, Set, Type
8
+
9
+ from fastapi import FastAPI
10
+ from fastapi.openapi.utils import get_openapi
11
+
12
+ from mcp_proxy_adapter.commands.command_registry import registry
13
+ from mcp_proxy_adapter.commands.base import Command
14
+ from mcp_proxy_adapter.core.logging import logger
15
+
16
+
17
+ class CustomOpenAPIGenerator:
18
+ """
19
+ Custom OpenAPI schema generator for compatibility with MCP-Proxy.
20
+
21
+ This generator creates an OpenAPI schema that matches the format expected by MCP-Proxy,
22
+ enabling dynamic command loading and proper tool representation in AI models.
23
+ """
24
+
25
+ def __init__(self):
26
+ """Initialize the generator."""
27
+ self.base_schema_path = Path(__file__).parent / "schemas" / "openapi_schema.json"
28
+ self.base_schema = self._load_base_schema()
29
+
30
+ def _load_base_schema(self) -> Dict[str, Any]:
31
+ """
32
+ Load the base OpenAPI schema from file.
33
+
34
+ Returns:
35
+ Dict containing the base OpenAPI schema.
36
+ """
37
+ with open(self.base_schema_path, "r", encoding="utf-8") as f:
38
+ return json.load(f)
39
+
40
+ def _add_commands_to_schema(self, schema: Dict[str, Any]) -> None:
41
+ """
42
+ Add all registered commands to the OpenAPI schema.
43
+
44
+ Args:
45
+ schema: The OpenAPI schema to update.
46
+ """
47
+ # Get all commands from the registry
48
+ commands = registry.get_all_commands()
49
+
50
+ # Add command names to the CommandRequest enum
51
+ schema["components"]["schemas"]["CommandRequest"]["properties"]["command"]["enum"] = [
52
+ cmd for cmd in commands.keys()
53
+ ]
54
+
55
+ # Add command parameters to oneOf
56
+ params_refs = []
57
+
58
+ for name, cmd_class in commands.items():
59
+ # Create schema for command parameters
60
+ param_schema_name = f"{name.capitalize()}Params"
61
+ schema["components"]["schemas"][param_schema_name] = self._create_params_schema(cmd_class)
62
+
63
+ # Add to oneOf
64
+ params_refs.append({"$ref": f"#/components/schemas/{param_schema_name}"})
65
+
66
+ # Add null option for commands without parameters
67
+ params_refs.append({"type": "null"})
68
+
69
+ # Set oneOf for params
70
+ schema["components"]["schemas"]["CommandRequest"]["properties"]["params"]["oneOf"] = params_refs
71
+
72
+ def _create_params_schema(self, cmd_class: Type[Command]) -> Dict[str, Any]:
73
+ """
74
+ Create a schema for command parameters.
75
+
76
+ Args:
77
+ cmd_class: The command class.
78
+
79
+ Returns:
80
+ Dict containing the parameter schema.
81
+ """
82
+ # Get command schema
83
+ cmd_schema = cmd_class.get_schema()
84
+
85
+ # Add title and description
86
+ cmd_schema["title"] = f"Parameters for {cmd_class.name}"
87
+ cmd_schema["description"] = f"Parameters for the {cmd_class.name} command"
88
+
89
+ return cmd_schema
90
+
91
+ def generate(self) -> Dict[str, Any]:
92
+ """
93
+ Generate the complete OpenAPI schema compatible with MCP-Proxy.
94
+
95
+ Returns:
96
+ Dict containing the complete OpenAPI schema.
97
+ """
98
+ # Deep copy the base schema to avoid modifying it
99
+ schema = deepcopy(self.base_schema)
100
+
101
+ # Add commands to schema
102
+ self._add_commands_to_schema(schema)
103
+
104
+ logger.info(f"Generated OpenAPI schema with {len(registry.get_all_commands())} commands")
105
+
106
+ return schema
107
+
108
+
109
+ def custom_openapi(app: FastAPI) -> Dict[str, Any]:
110
+ """
111
+ Create a custom OpenAPI schema for the FastAPI application.
112
+
113
+ Args:
114
+ app: The FastAPI application.
115
+
116
+ Returns:
117
+ Dict containing the custom OpenAPI schema.
118
+ """
119
+ generator = CustomOpenAPIGenerator()
120
+ openapi_schema = generator.generate()
121
+
122
+ # Cache the schema
123
+ app.openapi_schema = openapi_schema
124
+
125
+ return openapi_schema