mcp-proxy-adapter 3.1.6__py3-none-any.whl → 4.1.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 (118) hide show
  1. mcp_proxy_adapter/api/app.py +65 -27
  2. mcp_proxy_adapter/api/handlers.py +1 -1
  3. mcp_proxy_adapter/api/middleware/error_handling.py +11 -10
  4. mcp_proxy_adapter/api/tool_integration.py +5 -2
  5. mcp_proxy_adapter/api/tools.py +3 -3
  6. mcp_proxy_adapter/commands/base.py +19 -1
  7. mcp_proxy_adapter/commands/command_registry.py +254 -8
  8. mcp_proxy_adapter/commands/hooks.py +260 -0
  9. mcp_proxy_adapter/commands/reload_command.py +211 -0
  10. mcp_proxy_adapter/commands/reload_settings_command.py +125 -0
  11. mcp_proxy_adapter/commands/settings_command.py +189 -0
  12. mcp_proxy_adapter/config.py +16 -1
  13. mcp_proxy_adapter/core/__init__.py +44 -0
  14. mcp_proxy_adapter/core/logging.py +87 -34
  15. mcp_proxy_adapter/core/settings.py +376 -0
  16. mcp_proxy_adapter/core/utils.py +2 -2
  17. mcp_proxy_adapter/custom_openapi.py +81 -2
  18. mcp_proxy_adapter/examples/README.md +124 -0
  19. mcp_proxy_adapter/examples/__init__.py +7 -0
  20. mcp_proxy_adapter/examples/basic_server/README.md +60 -0
  21. mcp_proxy_adapter/examples/basic_server/__init__.py +7 -0
  22. mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +39 -0
  23. mcp_proxy_adapter/examples/basic_server/config.json +35 -0
  24. mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +238 -0
  25. mcp_proxy_adapter/examples/basic_server/server.py +98 -0
  26. mcp_proxy_adapter/examples/custom_commands/README.md +127 -0
  27. mcp_proxy_adapter/examples/custom_commands/__init__.py +27 -0
  28. mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +250 -0
  29. mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +6 -0
  30. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +103 -0
  31. mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +111 -0
  32. mcp_proxy_adapter/examples/custom_commands/config.json +62 -0
  33. mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +169 -0
  34. mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +215 -0
  35. mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +76 -0
  36. mcp_proxy_adapter/examples/custom_commands/custom_settings.json +96 -0
  37. mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +241 -0
  38. mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +135 -0
  39. mcp_proxy_adapter/examples/custom_commands/echo_command.py +122 -0
  40. mcp_proxy_adapter/examples/custom_commands/hooks.py +230 -0
  41. mcp_proxy_adapter/examples/custom_commands/intercept_command.py +123 -0
  42. mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +103 -0
  43. mcp_proxy_adapter/examples/custom_commands/server.py +223 -0
  44. mcp_proxy_adapter/examples/custom_commands/test_hooks.py +176 -0
  45. mcp_proxy_adapter/examples/deployment/README.md +49 -0
  46. mcp_proxy_adapter/examples/deployment/__init__.py +7 -0
  47. mcp_proxy_adapter/examples/deployment/config.development.json +8 -0
  48. {examples/basic_example → mcp_proxy_adapter/examples/deployment}/config.json +11 -7
  49. mcp_proxy_adapter/examples/deployment/config.production.json +12 -0
  50. mcp_proxy_adapter/examples/deployment/config.staging.json +11 -0
  51. mcp_proxy_adapter/examples/deployment/docker-compose.yml +31 -0
  52. mcp_proxy_adapter/examples/deployment/run.sh +43 -0
  53. mcp_proxy_adapter/examples/deployment/run_docker.sh +84 -0
  54. mcp_proxy_adapter/openapi.py +3 -2
  55. mcp_proxy_adapter/tests/api/test_custom_openapi.py +617 -0
  56. mcp_proxy_adapter/tests/api/test_handlers.py +522 -0
  57. mcp_proxy_adapter/tests/api/test_schemas.py +546 -0
  58. mcp_proxy_adapter/tests/api/test_tool_integration.py +531 -0
  59. mcp_proxy_adapter/tests/unit/test_base_command.py +391 -85
  60. mcp_proxy_adapter/version.py +1 -1
  61. {mcp_proxy_adapter-3.1.6.dist-info → mcp_proxy_adapter-4.1.0.dist-info}/METADATA +3 -3
  62. mcp_proxy_adapter-4.1.0.dist-info/RECORD +110 -0
  63. {mcp_proxy_adapter-3.1.6.dist-info → mcp_proxy_adapter-4.1.0.dist-info}/WHEEL +1 -1
  64. {mcp_proxy_adapter-3.1.6.dist-info → mcp_proxy_adapter-4.1.0.dist-info}/top_level.txt +0 -1
  65. examples/__init__.py +0 -19
  66. examples/anti_patterns/README.md +0 -51
  67. examples/anti_patterns/__init__.py +0 -9
  68. examples/anti_patterns/bad_design/README.md +0 -72
  69. examples/anti_patterns/bad_design/global_state.py +0 -170
  70. examples/anti_patterns/bad_design/monolithic_command.py +0 -272
  71. examples/basic_example/README.md +0 -245
  72. examples/basic_example/__init__.py +0 -8
  73. examples/basic_example/commands/__init__.py +0 -5
  74. examples/basic_example/commands/echo_command.py +0 -95
  75. examples/basic_example/commands/math_command.py +0 -151
  76. examples/basic_example/commands/time_command.py +0 -152
  77. examples/basic_example/docs/EN/README.md +0 -177
  78. examples/basic_example/docs/RU/README.md +0 -177
  79. examples/basic_example/server.py +0 -151
  80. examples/basic_example/tests/conftest.py +0 -243
  81. examples/check_vstl_schema.py +0 -106
  82. examples/commands/echo_command.py +0 -52
  83. examples/commands/echo_command_di.py +0 -152
  84. examples/commands/echo_result.py +0 -65
  85. examples/commands/get_date_command.py +0 -98
  86. examples/commands/new_uuid4_command.py +0 -91
  87. examples/complete_example/Dockerfile +0 -24
  88. examples/complete_example/README.md +0 -92
  89. examples/complete_example/__init__.py +0 -8
  90. examples/complete_example/commands/__init__.py +0 -5
  91. examples/complete_example/commands/system_command.py +0 -328
  92. examples/complete_example/config.json +0 -41
  93. examples/complete_example/configs/config.dev.yaml +0 -40
  94. examples/complete_example/configs/config.docker.yaml +0 -40
  95. examples/complete_example/docker-compose.yml +0 -35
  96. examples/complete_example/requirements.txt +0 -20
  97. examples/complete_example/server.py +0 -113
  98. examples/di_example/.pytest_cache/README.md +0 -8
  99. examples/di_example/server.py +0 -249
  100. examples/fix_vstl_help.py +0 -123
  101. examples/minimal_example/README.md +0 -65
  102. examples/minimal_example/__init__.py +0 -8
  103. examples/minimal_example/config.json +0 -14
  104. examples/minimal_example/main.py +0 -136
  105. examples/minimal_example/simple_server.py +0 -163
  106. examples/minimal_example/tests/conftest.py +0 -171
  107. examples/minimal_example/tests/test_hello_command.py +0 -111
  108. examples/minimal_example/tests/test_integration.py +0 -181
  109. examples/patch_vstl_service.py +0 -105
  110. examples/patch_vstl_service_mcp.py +0 -108
  111. examples/server.py +0 -69
  112. examples/simple_server.py +0 -128
  113. examples/test_package_3.1.4.py +0 -177
  114. examples/test_server.py +0 -134
  115. examples/tool_description_example.py +0 -82
  116. mcp_proxy_adapter/py.typed +0 -0
  117. mcp_proxy_adapter-3.1.6.dist-info/RECORD +0 -118
  118. {mcp_proxy_adapter-3.1.6.dist-info → mcp_proxy_adapter-4.1.0.dist-info}/licenses/LICENSE +0 -0
@@ -122,15 +122,28 @@ def setup_logging(
122
122
  # Get parameters from configuration if not explicitly specified
123
123
  level = level or config.get("logging.level", "INFO")
124
124
  log_file = log_file or config.get("logging.file")
125
- rotation_type = rotation_type or config.get("logging.rotation.type", "size")
125
+ rotation_type = rotation_type or "size" # Default to size-based rotation
126
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)
127
+ # Get log directory and file settings from config
128
+ log_dir = config.get("logging.log_dir", "./logs")
129
+ log_file_name = config.get("logging.log_file", "mcp_proxy_adapter.log")
130
+ error_log_file = config.get("logging.error_log_file", "mcp_proxy_adapter_error.log")
131
+ access_log_file = config.get("logging.access_log_file", "mcp_proxy_adapter_access.log")
130
132
 
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)
133
+ # Get rotation settings from config
134
+ max_file_size_str = config.get("logging.max_file_size", "10MB")
135
+ backup_count = backup_count or config.get("logging.backup_count", 5)
136
+
137
+ # Parse max file size (e.g., "10MB" -> 10 * 1024 * 1024)
138
+ max_bytes = max_bytes or _parse_file_size(max_file_size_str)
139
+
140
+ # Get format settings
141
+ log_format = config.get("logging.format", "%(asctime)s - %(name)s - %(levelname)s - %(message)s")
142
+ date_format = config.get("logging.date_format", "%Y-%m-%d %H:%M:%S")
143
+
144
+ # Get output settings
145
+ console_output = config.get("logging.console_output", True)
146
+ file_output = config.get("logging.file_output", True)
134
147
 
135
148
  # Convert string logging level to constant
136
149
  numeric_level = getattr(logging, level.upper(), None)
@@ -142,42 +155,60 @@ def setup_logging(
142
155
  logger.setLevel(numeric_level)
143
156
  logger.handlers = [] # Clear handlers in case of repeated call
144
157
 
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):
158
+ # Create formatter
159
+ formatter = logging.Formatter(log_format, date_format)
160
+
161
+ # Create console handler if enabled
162
+ if console_output:
163
+ console_handler = logging.StreamHandler(sys.stdout)
164
+ console_handler.setLevel(numeric_level)
165
+ console_handler.setFormatter(CustomFormatter())
166
+ logger.addHandler(console_handler)
167
+
168
+ # Create file handlers if file output is enabled
169
+ if file_output and log_dir:
170
+ # Create directory for log files if it doesn't exist
171
+ if not os.path.exists(log_dir):
156
172
  os.makedirs(log_dir, exist_ok=True)
157
173
 
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,
174
+ # Main log file
175
+ if log_file_name:
176
+ main_log_path = os.path.join(log_dir, log_file_name)
177
+ main_handler = RotatingFileHandler(
178
+ main_log_path,
179
+ maxBytes=max_bytes,
180
+ backupCount=backup_count,
181
+ encoding="utf-8"
182
+ )
183
+ main_handler.setLevel(numeric_level)
184
+ main_handler.setFormatter(formatter)
185
+ logger.addHandler(main_handler)
186
+
187
+ # Error log file
188
+ if error_log_file:
189
+ error_log_path = os.path.join(log_dir, error_log_file)
190
+ error_handler = RotatingFileHandler(
191
+ error_log_path,
192
+ maxBytes=max_bytes,
164
193
  backupCount=backup_count,
165
194
  encoding="utf-8"
166
195
  )
167
- else: # Default to size-based rotation
168
- file_handler = RotatingFileHandler(
169
- log_file,
196
+ error_handler.setLevel(logging.ERROR)
197
+ error_handler.setFormatter(formatter)
198
+ logger.addHandler(error_handler)
199
+
200
+ # Access log file (for HTTP requests)
201
+ if access_log_file:
202
+ access_log_path = os.path.join(log_dir, access_log_file)
203
+ access_handler = RotatingFileHandler(
204
+ access_log_path,
170
205
  maxBytes=max_bytes,
171
206
  backupCount=backup_count,
172
207
  encoding="utf-8"
173
208
  )
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)
209
+ access_handler.setLevel(logging.INFO)
210
+ access_handler.setFormatter(formatter)
211
+ logger.addHandler(access_handler)
181
212
 
182
213
  # Configure loggers for external libraries
183
214
  log_levels = config.get("logging.levels", {})
@@ -188,6 +219,28 @@ def setup_logging(
188
219
  return logger
189
220
 
190
221
 
222
+ def _parse_file_size(size_str: str) -> int:
223
+ """
224
+ Parse file size string to bytes.
225
+
226
+ Args:
227
+ size_str: Size string (e.g., "10MB", "1GB", "100KB")
228
+
229
+ Returns:
230
+ Size in bytes
231
+ """
232
+ size_str = size_str.upper()
233
+ if size_str.endswith("KB"):
234
+ return int(size_str[:-2]) * 1024
235
+ elif size_str.endswith("MB"):
236
+ return int(size_str[:-2]) * 1024 * 1024
237
+ elif size_str.endswith("GB"):
238
+ return int(size_str[:-2]) * 1024 * 1024 * 1024
239
+ else:
240
+ # Assume bytes if no unit specified
241
+ return int(size_str)
242
+
243
+
191
244
  def get_logger(name: str) -> logging.Logger:
192
245
  """
193
246
  Get a logger with the specified name.
@@ -0,0 +1,376 @@
1
+ """
2
+ Settings management for the MCP Proxy Adapter framework.
3
+ Provides utilities for reading and managing framework settings from configuration.
4
+ """
5
+
6
+ from typing import Any, Dict, Optional, Union
7
+ from mcp_proxy_adapter.config import config
8
+
9
+
10
+ class Settings:
11
+ """
12
+ Settings management class for the framework.
13
+ Provides easy access to configuration values with type conversion and validation.
14
+ """
15
+
16
+ # Store custom settings as a class variable
17
+ _custom_settings: Dict[str, Any] = {}
18
+
19
+ @classmethod
20
+ def add_custom_settings(cls, settings: Dict[str, Any]) -> None:
21
+ """
22
+ Add custom settings to the settings manager.
23
+
24
+ Args:
25
+ settings: Dictionary with custom settings
26
+ """
27
+ cls._custom_settings.update(settings)
28
+
29
+ @classmethod
30
+ def get_custom_settings(cls) -> Dict[str, Any]:
31
+ """
32
+ Get all custom settings.
33
+
34
+ Returns:
35
+ Dictionary with all custom settings
36
+ """
37
+ return cls._custom_settings.copy()
38
+
39
+ @classmethod
40
+ def get_custom_setting_value(cls, key: str, default: Any = None) -> Any:
41
+ """
42
+ Get custom setting value.
43
+
44
+ Args:
45
+ key: Setting key
46
+ default: Default value if key not found
47
+
48
+ Returns:
49
+ Setting value
50
+ """
51
+ return cls._custom_settings.get(key, default)
52
+
53
+ @classmethod
54
+ def set_custom_setting_value(cls, key: str, value: Any) -> None:
55
+ """
56
+ Set custom setting value.
57
+
58
+ Args:
59
+ key: Setting key
60
+ value: Value to set
61
+ """
62
+ cls._custom_settings[key] = value
63
+
64
+ @classmethod
65
+ def clear_custom_settings(cls) -> None:
66
+ """
67
+ Clear all custom settings.
68
+ """
69
+ cls._custom_settings.clear()
70
+
71
+ @staticmethod
72
+ def get_server_settings() -> Dict[str, Any]:
73
+ """
74
+ Get server configuration settings.
75
+
76
+ Returns:
77
+ Dictionary with server settings
78
+ """
79
+ return {
80
+ "host": config.get("server.host", "0.0.0.0"),
81
+ "port": config.get("server.port", 8000),
82
+ "debug": config.get("server.debug", False),
83
+ "log_level": config.get("server.log_level", "INFO")
84
+ }
85
+
86
+ @staticmethod
87
+ def get_logging_settings() -> Dict[str, Any]:
88
+ """
89
+ Get logging configuration settings.
90
+
91
+ Returns:
92
+ Dictionary with logging settings
93
+ """
94
+ return {
95
+ "level": config.get("logging.level", "INFO"),
96
+ "file": config.get("logging.file"),
97
+ "log_dir": config.get("logging.log_dir", "./logs"),
98
+ "log_file": config.get("logging.log_file", "mcp_proxy_adapter.log"),
99
+ "error_log_file": config.get("logging.error_log_file", "mcp_proxy_adapter_error.log"),
100
+ "access_log_file": config.get("logging.access_log_file", "mcp_proxy_adapter_access.log"),
101
+ "max_file_size": config.get("logging.max_file_size", "10MB"),
102
+ "backup_count": config.get("logging.backup_count", 5),
103
+ "format": config.get("logging.format", "%(asctime)s - %(name)s - %(levelname)s - %(message)s"),
104
+ "date_format": config.get("logging.date_format", "%Y-%m-%d %H:%M:%S"),
105
+ "console_output": config.get("logging.console_output", True),
106
+ "file_output": config.get("logging.file_output", True)
107
+ }
108
+
109
+ @staticmethod
110
+ def get_commands_settings() -> Dict[str, Any]:
111
+ """
112
+ Get commands configuration settings.
113
+
114
+ Returns:
115
+ Dictionary with commands settings
116
+ """
117
+ return {
118
+ "auto_discovery": config.get("commands.auto_discovery", True),
119
+ "discovery_path": config.get("commands.discovery_path", "mcp_proxy_adapter.commands"),
120
+ "custom_commands_path": config.get("commands.custom_commands_path")
121
+ }
122
+
123
+ @staticmethod
124
+ def get_custom_setting(key: str, default: Any = None) -> Any:
125
+ """
126
+ Get custom setting from configuration.
127
+
128
+ Args:
129
+ key: Configuration key in dot notation (e.g., "custom.feature_enabled")
130
+ default: Default value if key not found
131
+
132
+ Returns:
133
+ Configuration value
134
+ """
135
+ return config.get(key, default)
136
+
137
+ @staticmethod
138
+ def get_all_settings() -> Dict[str, Any]:
139
+ """
140
+ Get all configuration settings including custom settings.
141
+
142
+ Returns:
143
+ Dictionary with all configuration settings
144
+ """
145
+ all_settings = config.get_all()
146
+ all_settings['custom_settings'] = Settings._custom_settings
147
+ return all_settings
148
+
149
+ @staticmethod
150
+ def set_custom_setting(key: str, value: Any) -> None:
151
+ """
152
+ Set custom setting in configuration.
153
+
154
+ Args:
155
+ key: Configuration key in dot notation
156
+ value: Value to set
157
+ """
158
+ config.set(key, value)
159
+
160
+ @staticmethod
161
+ def reload_config() -> None:
162
+ """
163
+ Reload configuration from file and environment variables.
164
+ """
165
+ config.load_config()
166
+
167
+
168
+ class ServerSettings:
169
+ """
170
+ Server-specific settings helper.
171
+ """
172
+
173
+ @staticmethod
174
+ def get_host() -> str:
175
+ """Get server host."""
176
+ return config.get("server.host", "0.0.0.0")
177
+
178
+ @staticmethod
179
+ def get_port() -> int:
180
+ """Get server port."""
181
+ return config.get("server.port", 8000)
182
+
183
+ @staticmethod
184
+ def get_debug() -> bool:
185
+ """Get debug mode."""
186
+ return config.get("server.debug", False)
187
+
188
+ @staticmethod
189
+ def get_log_level() -> str:
190
+ """Get log level."""
191
+ return config.get("server.log_level", "INFO")
192
+
193
+
194
+ class LoggingSettings:
195
+ """
196
+ Logging-specific settings helper.
197
+ """
198
+
199
+ @staticmethod
200
+ def get_level() -> str:
201
+ """Get logging level."""
202
+ return config.get("logging.level", "INFO")
203
+
204
+ @staticmethod
205
+ def get_log_dir() -> str:
206
+ """Get log directory."""
207
+ return config.get("logging.log_dir", "./logs")
208
+
209
+ @staticmethod
210
+ def get_log_file() -> Optional[str]:
211
+ """Get main log file name."""
212
+ return config.get("logging.log_file", "mcp_proxy_adapter.log")
213
+
214
+ @staticmethod
215
+ def get_error_log_file() -> Optional[str]:
216
+ """Get error log file name."""
217
+ return config.get("logging.error_log_file", "mcp_proxy_adapter_error.log")
218
+
219
+ @staticmethod
220
+ def get_access_log_file() -> Optional[str]:
221
+ """Get access log file name."""
222
+ return config.get("logging.access_log_file", "mcp_proxy_adapter_access.log")
223
+
224
+ @staticmethod
225
+ def get_max_file_size() -> str:
226
+ """Get max file size."""
227
+ return config.get("logging.max_file_size", "10MB")
228
+
229
+ @staticmethod
230
+ def get_backup_count() -> int:
231
+ """Get backup count."""
232
+ return config.get("logging.backup_count", 5)
233
+
234
+ @staticmethod
235
+ def get_format() -> str:
236
+ """Get log format."""
237
+ return config.get("logging.format", "%(asctime)s - %(name)s - %(levelname)s - %(message)s")
238
+
239
+ @staticmethod
240
+ def get_date_format() -> str:
241
+ """Get date format."""
242
+ return config.get("logging.date_format", "%Y-%m-%d %H:%M:%S")
243
+
244
+ @staticmethod
245
+ def get_console_output() -> bool:
246
+ """Get console output setting."""
247
+ return config.get("logging.console_output", True)
248
+
249
+ @staticmethod
250
+ def get_file_output() -> bool:
251
+ """Get file output setting."""
252
+ return config.get("logging.file_output", True)
253
+
254
+
255
+ class CommandsSettings:
256
+ """
257
+ Commands-specific settings helper.
258
+ """
259
+
260
+ @staticmethod
261
+ def get_auto_discovery() -> bool:
262
+ """Get auto discovery setting."""
263
+ return config.get("commands.auto_discovery", True)
264
+
265
+ @staticmethod
266
+ def get_discovery_path() -> str:
267
+ """Get discovery path."""
268
+ return config.get("commands.discovery_path", "mcp_proxy_adapter.commands")
269
+
270
+ @staticmethod
271
+ def get_custom_commands_path() -> Optional[str]:
272
+ """Get custom commands path."""
273
+ return config.get("commands.custom_commands_path")
274
+
275
+
276
+ # Convenience functions for easy access
277
+ def get_server_host() -> str:
278
+ """Get server host."""
279
+ return ServerSettings.get_host()
280
+
281
+
282
+ def get_server_port() -> int:
283
+ """Get server port."""
284
+ return ServerSettings.get_port()
285
+
286
+
287
+ def get_server_debug() -> bool:
288
+ """Get server debug mode."""
289
+ return ServerSettings.get_debug()
290
+
291
+
292
+ def get_logging_level() -> str:
293
+ """Get logging level."""
294
+ return LoggingSettings.get_level()
295
+
296
+
297
+ def get_logging_dir() -> str:
298
+ """Get logging directory."""
299
+ return LoggingSettings.get_log_dir()
300
+
301
+
302
+ def get_auto_discovery() -> bool:
303
+ """Get auto discovery setting."""
304
+ return CommandsSettings.get_auto_discovery()
305
+
306
+
307
+ def get_discovery_path() -> str:
308
+ """Get discovery path."""
309
+ return CommandsSettings.get_discovery_path()
310
+
311
+
312
+ def get_setting(key: str, default: Any = None) -> Any:
313
+ """Get any setting by key."""
314
+ return Settings.get_custom_setting(key, default)
315
+
316
+
317
+ def set_setting(key: str, value: Any) -> None:
318
+ """Set any setting by key."""
319
+ Settings.set_custom_setting(key, value)
320
+
321
+
322
+ def reload_settings() -> None:
323
+ """Reload all settings from configuration."""
324
+ Settings.reload_config()
325
+
326
+
327
+ def add_custom_settings(settings: Dict[str, Any]) -> None:
328
+ """
329
+ Add custom settings to the settings manager.
330
+
331
+ Args:
332
+ settings: Dictionary with custom settings
333
+ """
334
+ Settings.add_custom_settings(settings)
335
+
336
+
337
+ def get_custom_settings() -> Dict[str, Any]:
338
+ """
339
+ Get all custom settings.
340
+
341
+ Returns:
342
+ Dictionary with all custom settings
343
+ """
344
+ return Settings.get_custom_settings()
345
+
346
+
347
+ def get_custom_setting_value(key: str, default: Any = None) -> Any:
348
+ """
349
+ Get custom setting value.
350
+
351
+ Args:
352
+ key: Setting key
353
+ default: Default value if key not found
354
+
355
+ Returns:
356
+ Setting value
357
+ """
358
+ return Settings.get_custom_setting_value(key, default)
359
+
360
+
361
+ def set_custom_setting_value(key: str, value: Any) -> None:
362
+ """
363
+ Set custom setting value.
364
+
365
+ Args:
366
+ key: Setting key
367
+ value: Value to set
368
+ """
369
+ Settings.set_custom_setting_value(key, value)
370
+
371
+
372
+ def clear_custom_settings() -> None:
373
+ """
374
+ Clear all custom settings.
375
+ """
376
+ Settings.clear_custom_settings()
@@ -8,7 +8,7 @@ import os
8
8
  import sys
9
9
  import time
10
10
  import uuid
11
- from datetime import datetime
11
+ from datetime import datetime, timezone
12
12
  from typing import Any, Dict, List, Optional, Tuple, Union
13
13
 
14
14
  from mcp_proxy_adapter.core.logging import logger
@@ -45,7 +45,7 @@ def format_datetime(dt: Optional[datetime] = None, format_str: str = "%Y-%m-%dT%
45
45
  Returns:
46
46
  Formatted date/time string.
47
47
  """
48
- dt = dt or datetime.utcnow()
48
+ dt = dt or datetime.now(timezone.utc)
49
49
  return dt.strftime(format_str)
50
50
 
51
51
 
@@ -4,7 +4,7 @@ Custom OpenAPI schema generator for MCP Microservice compatible with MCP-Proxy.
4
4
  import json
5
5
  from copy import deepcopy
6
6
  from pathlib import Path
7
- from typing import Any, Dict, List, Optional, Set, Type
7
+ from typing import Any, Dict, List, Optional, Set, Type, Callable
8
8
 
9
9
  from fastapi import FastAPI
10
10
  from fastapi.openapi.utils import get_openapi
@@ -55,6 +55,15 @@ class CustomOpenAPIGenerator:
55
55
  # Get all commands from the registry
56
56
  commands = registry.get_all_commands()
57
57
 
58
+ # Ensure CommandRequest exists in schemas
59
+ if "CommandRequest" not in schema["components"]["schemas"]:
60
+ schema["components"]["schemas"]["CommandRequest"] = {
61
+ "properties": {
62
+ "command": {"type": "string", "enum": []},
63
+ "params": {"type": "object", "oneOf": []}
64
+ }
65
+ }
66
+
58
67
  # Add command names to the CommandRequest enum
59
68
  schema["components"]["schemas"]["CommandRequest"]["properties"]["command"]["enum"] = [
60
69
  cmd for cmd in commands.keys()
@@ -284,4 +293,74 @@ def custom_openapi(app: FastAPI) -> Dict[str, Any]:
284
293
  # Cache the schema
285
294
  app.openapi_schema = openapi_schema
286
295
 
287
- return openapi_schema
296
+ return openapi_schema
297
+
298
+
299
+ # Registry for custom OpenAPI generators
300
+ _openapi_generators: Dict[str, Callable] = {}
301
+
302
+
303
+ def register_openapi_generator(name: str, generator_func: Callable[[FastAPI], Dict[str, Any]]) -> None:
304
+ """
305
+ Register a custom OpenAPI generator.
306
+
307
+ Args:
308
+ name: Generator name.
309
+ generator_func: Function that generates OpenAPI schema.
310
+ """
311
+ _openapi_generators[name] = generator_func
312
+ logger.info(f"Registered custom OpenAPI generator: {name}")
313
+
314
+
315
+ def get_openapi_generator(name: str) -> Optional[Callable[[FastAPI], Dict[str, Any]]]:
316
+ """
317
+ Get a custom OpenAPI generator by name.
318
+
319
+ Args:
320
+ name: Generator name.
321
+
322
+ Returns:
323
+ Generator function or None if not found.
324
+ """
325
+ return _openapi_generators.get(name)
326
+
327
+
328
+ def list_openapi_generators() -> List[str]:
329
+ """
330
+ Get list of registered OpenAPI generators.
331
+
332
+ Returns:
333
+ List of generator names.
334
+ """
335
+ return list(_openapi_generators.keys())
336
+
337
+
338
+ def custom_openapi_with_fallback(app: FastAPI) -> Dict[str, Any]:
339
+ """
340
+ EN:
341
+ Create a custom OpenAPI schema for the FastAPI application.
342
+ Checks for custom generators first, then falls back to default generator.
343
+ Passes app's title, description, and version to the generator.
344
+
345
+ RU:
346
+ Создаёт кастомную OpenAPI-схему для FastAPI-приложения.
347
+ Сначала проверяет наличие кастомных генераторов, затем использует встроенный генератор.
348
+ Передаёт параметры title, description и version из приложения в генератор схемы.
349
+
350
+ Args:
351
+ app: The FastAPI application / FastAPI-приложение
352
+
353
+ Returns:
354
+ Dict containing the custom OpenAPI schema / Словарь с кастомной OpenAPI-схемой
355
+ """
356
+ # Check if there are any custom generators
357
+ if _openapi_generators:
358
+ # Use the first registered generator
359
+ generator_name = list(_openapi_generators.keys())[0]
360
+ generator_func = _openapi_generators[generator_name]
361
+ logger.info(f"Using custom OpenAPI generator: {generator_name}")
362
+ return generator_func(app)
363
+
364
+ # Fall back to default generator
365
+ logger.info("Using default OpenAPI generator")
366
+ return custom_openapi(app)