mcp-proxy-adapter 2.1.2__py3-none-any.whl → 2.1.3__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 (50) hide show
  1. {mcp_proxy_adapter-2.1.2.dist-info → mcp_proxy_adapter-2.1.3.dist-info}/METADATA +1 -1
  2. mcp_proxy_adapter-2.1.3.dist-info/RECORD +18 -0
  3. mcp_proxy_adapter-2.1.3.dist-info/top_level.txt +1 -0
  4. docs/README.md +0 -172
  5. docs/README_ru.md +0 -172
  6. docs/architecture.md +0 -251
  7. docs/architecture_ru.md +0 -343
  8. docs/command_development.md +0 -250
  9. docs/command_development_ru.md +0 -593
  10. docs/deployment.md +0 -251
  11. docs/deployment_ru.md +0 -1298
  12. docs/examples.md +0 -254
  13. docs/examples_ru.md +0 -401
  14. docs/mcp_proxy_adapter.md +0 -251
  15. docs/mcp_proxy_adapter_ru.md +0 -405
  16. docs/quickstart.md +0 -251
  17. docs/quickstart_ru.md +0 -397
  18. docs/testing.md +0 -255
  19. docs/testing_ru.md +0 -469
  20. docs/validation_ru.md +0 -287
  21. examples/analyze_config.py +0 -141
  22. examples/basic_integration.py +0 -161
  23. examples/docstring_and_schema_example.py +0 -60
  24. examples/extension_example.py +0 -60
  25. examples/help_best_practices.py +0 -67
  26. examples/help_usage.py +0 -64
  27. examples/mcp_proxy_client.py +0 -131
  28. examples/mcp_proxy_config.json +0 -175
  29. examples/openapi_server.py +0 -369
  30. examples/project_structure_example.py +0 -47
  31. examples/testing_example.py +0 -53
  32. mcp_proxy_adapter-2.1.2.dist-info/RECORD +0 -61
  33. mcp_proxy_adapter-2.1.2.dist-info/top_level.txt +0 -5
  34. scripts/code_analyzer/code_analyzer.py +0 -328
  35. scripts/code_analyzer/register_commands.py +0 -446
  36. scripts/publish.py +0 -85
  37. tests/conftest.py +0 -12
  38. tests/test_adapter.py +0 -529
  39. tests/test_adapter_coverage.py +0 -274
  40. tests/test_basic_dispatcher.py +0 -169
  41. tests/test_command_registry.py +0 -328
  42. tests/test_examples.py +0 -32
  43. tests/test_mcp_proxy_adapter.py +0 -568
  44. tests/test_mcp_proxy_adapter_basic.py +0 -262
  45. tests/test_part1.py +0 -348
  46. tests/test_part2.py +0 -524
  47. tests/test_schema.py +0 -358
  48. tests/test_simple_adapter.py +0 -251
  49. {mcp_proxy_adapter-2.1.2.dist-info → mcp_proxy_adapter-2.1.3.dist-info}/WHEEL +0 -0
  50. {mcp_proxy_adapter-2.1.2.dist-info → mcp_proxy_adapter-2.1.3.dist-info}/licenses/LICENSE +0 -0
@@ -1,446 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Инструмент для автоматической регистрации всех команд с метаданными.
4
- Проходит по всем модулям с командами и регистрирует их в диспетчере.
5
- """
6
- import os
7
- import importlib
8
- import inspect
9
- import sys
10
- from typing import Dict, Any, Optional, List, Tuple, get_type_hints, get_origin, get_args
11
- import docstring_parser
12
-
13
- # Добавляем корневую директорию проекта в sys.path
14
- sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
15
-
16
- from utils.command_dispatcher import dispatcher
17
- import commands
18
- from commands import get_all_commands, get_command_names
19
-
20
- def map_type_to_openapi(type_hint) -> str:
21
- """
22
- Преобразует тип Python в тип OpenAPI.
23
-
24
- Args:
25
- type_hint: Тип Python
26
-
27
- Returns:
28
- str: Строковое представление типа OpenAPI
29
- """
30
- # Проверяем на None
31
- if type_hint is None:
32
- return "object"
33
-
34
- # Обрабатываем примитивные типы
35
- primitives = {
36
- str: "string",
37
- int: "integer",
38
- float: "number",
39
- bool: "boolean",
40
- list: "array",
41
- dict: "object",
42
- Any: "object"
43
- }
44
-
45
- if type_hint in primitives:
46
- return primitives[type_hint]
47
-
48
- # Проверяем на обобщенные типы
49
- origin = get_origin(type_hint)
50
- if origin is not None:
51
- # Обрабатываем List[X], Dict[X, Y] и т.д.
52
- if origin in (list, List):
53
- return "array"
54
- elif origin in (dict, Dict):
55
- return "object"
56
- elif origin is Union:
57
- # Для Union берем первый тип, который не None
58
- args = get_args(type_hint)
59
- for arg in args:
60
- if arg is not type(None):
61
- return map_type_to_openapi(arg)
62
- return "object"
63
-
64
- # По умолчанию возвращаем object
65
- return "object"
66
-
67
- def extract_param_types_from_handler(handler) -> Dict[str, str]:
68
- """
69
- Извлекает типы параметров из аннотаций функции.
70
-
71
- Args:
72
- handler: Функция-обработчик
73
-
74
- Returns:
75
- Dict[str, str]: Словарь {имя_параметра: тип_openapi}
76
- """
77
- param_types = {}
78
-
79
- try:
80
- # Получаем аннотации типов
81
- type_hints = get_type_hints(handler)
82
-
83
- # Преобразуем типы Python в типы OpenAPI
84
- for param_name, param_type in type_hints.items():
85
- # Пропускаем return тип
86
- if param_name == 'return':
87
- continue
88
-
89
- # Преобразуем тип Python в тип OpenAPI
90
- openapi_type = map_type_to_openapi(param_type)
91
- param_types[param_name] = openapi_type
92
- except Exception as e:
93
- print(f" ⚠️ Ошибка при извлечении типов параметров: {str(e)}")
94
-
95
- return param_types
96
-
97
- def validate_docstring(handler, command_name: str) -> Tuple[bool, List[str]]:
98
- """
99
- Проверяет соответствие докстринга функции-обработчика её формальным параметрам.
100
-
101
- Args:
102
- handler: Функция-обработчик команды
103
- command_name: Имя команды
104
-
105
- Returns:
106
- Tuple[bool, List[str]]: Флаг валидности и список ошибок
107
- """
108
- errors = []
109
-
110
- # Получаем формальные параметры функции
111
- try:
112
- sig = inspect.signature(handler)
113
- formal_params = list(sig.parameters.keys())
114
-
115
- # Пропускаем параметр self для методов
116
- if formal_params and formal_params[0] == 'self':
117
- formal_params = formal_params[1:]
118
-
119
- # Парсим докстринг
120
- docstring = handler.__doc__ or ""
121
- parsed_doc = docstring_parser.parse(docstring)
122
-
123
- # Проверяем наличие описания функции
124
- if not parsed_doc.short_description and not parsed_doc.long_description:
125
- errors.append(f"Отсутствует описание функции {command_name}")
126
-
127
- # Получаем параметры из докстринга
128
- doc_params = {param.arg_name: param for param in parsed_doc.params}
129
-
130
- # Проверяем, что все формальные параметры описаны в докстринге
131
- for param in formal_params:
132
- if param not in doc_params and param != 'params': # 'params' - особый случай, может быть словарём всех параметров
133
- errors.append(f"Параметр '{param}' не описан в докстринге функции {command_name}")
134
-
135
- # Проверяем наличие returns в докстринге
136
- if not parsed_doc.returns and not any(t.type_name == 'Returns' for t in parsed_doc.meta):
137
- errors.append(f"Отсутствует описание возвращаемого значения в докстринге функции {command_name}")
138
-
139
- # Проверяем аннотации типов
140
- type_hints = get_type_hints(handler)
141
- for param in formal_params:
142
- if param not in type_hints and param != 'params':
143
- errors.append(f"Отсутствует аннотация типа для параметра '{param}' в функции {command_name}")
144
-
145
- return len(errors) == 0, errors
146
- except Exception as e:
147
- return False, [f"Ошибка при валидации докстринга: {str(e)}"]
148
-
149
- def extract_metadata_from_command(command_name: str, command_info: Dict[str, Any], handler=None):
150
- """
151
- Извлекает метаданные из информации о команде и сигнатуры функции-обработчика.
152
-
153
- Args:
154
- command_name: Имя команды
155
- command_info: Информация о команде
156
- handler: Функция-обработчик (опционально)
157
-
158
- Returns:
159
- dict: Метаданные для регистрации команды
160
- """
161
- metadata = {
162
- "description": command_info.get("description", f"Команда {command_name}"),
163
- "summary": command_name.replace("_", " ").title(),
164
- "params": {}
165
- }
166
-
167
- # Если есть обработчик, извлекаем типы параметров из него
168
- param_types_from_handler = {}
169
- if handler:
170
- param_types_from_handler = extract_param_types_from_handler(handler)
171
-
172
- # Если в команде определены параметры, добавляем их
173
- if "parameters" in command_info:
174
- for param_name, param_info in command_info["parameters"].items():
175
- # Начинаем с типа из метаданных команды
176
- param_type = param_info.get("type", "string")
177
-
178
- # Если есть тип из аннотации функции, используем его
179
- if param_name in param_types_from_handler:
180
- param_type = param_types_from_handler[param_name]
181
-
182
- metadata["params"][param_name] = {
183
- "type": param_type,
184
- "description": param_info.get("description", f"Параметр {param_name}"),
185
- "required": param_info.get("required", False)
186
- }
187
-
188
- if "default" in param_info:
189
- metadata["params"][param_name]["default"] = param_info["default"]
190
- # Если параметры не определены, но есть обработчик, извлекаем их из обработчика
191
- elif handler:
192
- # Получаем сигнатуру функции
193
- sig = inspect.signature(handler)
194
-
195
- # Проходим по параметрам функции
196
- for param_name, param in sig.parameters.items():
197
- # Пропускаем self для методов
198
- if param_name == 'self':
199
- continue
200
-
201
- # Если параметр называется params, предполагаем, что это словарь всех параметров
202
- if param_name == 'params':
203
- # В этом случае метаданные недоступны из сигнатуры
204
- continue
205
-
206
- # Определяем, является ли параметр обязательным
207
- required = param.default == inspect.Parameter.empty
208
-
209
- # Определяем тип параметра
210
- param_type = "string" # По умолчанию
211
- if param_name in param_types_from_handler:
212
- param_type = param_types_from_handler[param_name]
213
-
214
- # Получаем описание из докстринга
215
- description = f"Параметр {param_name}"
216
- try:
217
- # Парсим докстринг
218
- docstring = handler.__doc__ or ""
219
- parsed_doc = docstring_parser.parse(docstring)
220
-
221
- # Ищем описание параметра
222
- for doc_param in parsed_doc.params:
223
- if doc_param.arg_name == param_name:
224
- description = doc_param.description or description
225
- break
226
- except Exception:
227
- pass
228
-
229
- # Добавляем параметр в метаданные
230
- metadata["params"][param_name] = {
231
- "type": param_type,
232
- "description": description,
233
- "required": required
234
- }
235
-
236
- # Добавляем значение по умолчанию, если есть
237
- if param.default != inspect.Parameter.empty:
238
- metadata["params"][param_name]["default"] = param.default
239
-
240
- return metadata
241
-
242
- def validate_handler_metadata(handler, command_name: str, command_info: Dict[str, Any]) -> Tuple[bool, List[str]]:
243
- """
244
- Проверяет соответствие метаданных команды формальным параметрам функции-обработчика.
245
-
246
- Args:
247
- handler: Функция-обработчик команды
248
- command_name: Имя команды
249
- command_info: Информация о команде
250
-
251
- Returns:
252
- Tuple[bool, List[str]]: Флаг валидности и список ошибок
253
- """
254
- errors = []
255
-
256
- # Получаем формальные параметры функции
257
- sig = inspect.signature(handler)
258
- formal_params = list(sig.parameters.keys())
259
-
260
- # Пропускаем параметр self для методов
261
- if formal_params and formal_params[0] == 'self':
262
- formal_params = formal_params[1:]
263
-
264
- # Если функция принимает просто словарь params, пропускаем проверку
265
- if len(formal_params) == 1 and formal_params[0] == 'params':
266
- return True, []
267
-
268
- # Получаем типы параметров из аннотаций функции
269
- param_types = extract_param_types_from_handler(handler)
270
-
271
- # Проверяем соответствие метаданных команды формальным параметрам
272
- if "parameters" in command_info:
273
- for param_name, param_info in command_info["parameters"].items():
274
- # Проверяем наличие параметра в сигнатуре функции
275
- if param_name not in formal_params:
276
- errors.append(f"Параметр '{param_name}' указан в метаданных, но отсутствует в сигнатуре функции {command_name}")
277
- continue
278
-
279
- # Проверяем соответствие типов
280
- if param_name in param_types and "type" in param_info:
281
- openapi_type = param_info["type"]
282
- annotation_type = param_types[param_name]
283
-
284
- if openapi_type != annotation_type:
285
- errors.append(f"Тип параметра '{param_name}' в метаданных ({openapi_type}) не соответствует аннотации функции ({annotation_type})")
286
-
287
- # Проверяем, что все обязательные формальные параметры указаны в метаданных
288
- for param_name, param in sig.parameters.items():
289
- if param_name != 'self' and param.default == inspect.Parameter.empty:
290
- if param_name not in command_info.get("parameters", {}):
291
- errors.append(f"Обязательный параметр '{param_name}' не указан в метаданных команды {command_name}")
292
-
293
- return len(errors) == 0, errors
294
-
295
- def find_handler_function(command_name: str):
296
- """
297
- Находит функцию-обработчик для команды.
298
-
299
- Args:
300
- command_name: Имя команды
301
-
302
- Returns:
303
- function: Функция-обработчик команды или None, если не найдена
304
- """
305
- # Проверяем различные варианты имени функции
306
- function_names = [
307
- command_name, # имя_команды
308
- f"{command_name}_command", # имя_команды_command
309
- f"execute_{command_name}", # execute_имя_команды
310
- "execute" # execute (общий обработчик)
311
- ]
312
-
313
- # Пути для поиска функций
314
- command_paths = [
315
- f"commands.metadata.{command_name}",
316
- f"commands.search.{command_name}",
317
- f"commands.index.{command_name}",
318
- f"handlers.{command_name}_handlers",
319
- f"rpc.handlers"
320
- ]
321
-
322
- # Пытаемся импортировать модули и найти функцию
323
- for path in command_paths:
324
- try:
325
- module = importlib.import_module(path)
326
-
327
- for func_name in function_names:
328
- if hasattr(module, func_name):
329
- return getattr(module, func_name)
330
- except (ImportError, ModuleNotFoundError):
331
- continue
332
-
333
- return None
334
-
335
- def register_command(command_name: str, command_info: Dict[str, Any], strict: bool = True, auto_fix: bool = False):
336
- """
337
- Регистрирует команду в диспетчере с метаданными.
338
-
339
- Args:
340
- command_name: Имя команды
341
- command_info: Информация о команде
342
- strict: Если True, прерывает регистрацию при обнаружении ошибок
343
- auto_fix: Если True, пытается автоматически исправить несоответствия
344
- """
345
- # Находим функцию-обработчик
346
- handler = find_handler_function(command_name)
347
-
348
- if not handler:
349
- print(f"⚠️ Не удалось найти обработчик для команды '{command_name}'")
350
- return False
351
-
352
- # Проверяем соответствие документации и формальных параметров
353
- is_valid_doc, doc_errors = validate_docstring(handler, command_name)
354
- is_valid_meta, meta_errors = validate_handler_metadata(handler, command_name, command_info)
355
-
356
- # Выводим все ошибки
357
- if not is_valid_doc or not is_valid_meta:
358
- print(f"🚫 Ошибки в команде '{command_name}':")
359
-
360
- for error in doc_errors:
361
- print(f" - {error}")
362
-
363
- for error in meta_errors:
364
- print(f" - {error}")
365
-
366
- # В строгом режиме без автоисправления пропускаем регистрацию
367
- if strict and not auto_fix:
368
- print(f"❌ Регистрация команды '{command_name}' пропущена из-за ошибок")
369
- return False
370
-
371
- # Извлекаем метаданные, учитывая аннотации функции
372
- metadata = extract_metadata_from_command(command_name, command_info, handler if auto_fix else None)
373
-
374
- # Регистрируем команду в диспетчере
375
- print(f"📝 Регистрируем команду '{command_name}'")
376
- dispatcher.register_handler(
377
- command=command_name,
378
- handler=handler,
379
- description=metadata["description"],
380
- summary=metadata["summary"],
381
- params=metadata["params"]
382
- )
383
-
384
- return True
385
-
386
- def register_all_commands(strict: bool = True, auto_fix: bool = False):
387
- """
388
- Регистрирует все команды в диспетчере.
389
-
390
- Args:
391
- strict: Если True, прерывает регистрацию команды при обнаружении ошибок
392
- auto_fix: Если True, пытается автоматически исправить несоответствия
393
- """
394
- # Получаем все команды
395
- commands_info = get_all_commands()
396
-
397
- # Счетчики
398
- successful = 0
399
- failed = 0
400
- skipped = 0
401
-
402
- # Регистрируем каждую команду
403
- for command_name, command_info in commands_info.items():
404
- # Пропускаем команду help, она уже зарегистрирована
405
- if command_name == "help":
406
- skipped += 1
407
- continue
408
-
409
- if register_command(command_name, command_info, strict, auto_fix):
410
- successful += 1
411
- else:
412
- failed += 1
413
-
414
- print(f"\n✅ Итоги регистрации команд:")
415
- print(f" - Успешно: {successful}")
416
- print(f" - С ошибками: {failed}")
417
- print(f" - Пропущено: {skipped}")
418
- print(f" - Всего в диспетчере: {len(dispatcher.get_valid_commands())}")
419
-
420
- if failed > 0 and strict:
421
- print("\n⚠️ ВНИМАНИЕ: Некоторые команды не были зарегистрированы из-за ошибок")
422
- print(" Используйте register_all_commands(strict=False) для регистрации всех команд")
423
- print(" Или register_all_commands(auto_fix=True) для автоматического исправления ошибок")
424
-
425
- if __name__ == "__main__":
426
- print("🔍 Начинаем регистрацию команд...")
427
-
428
- # Проверяем аргументы командной строки
429
- strict_mode = True
430
- auto_fix_mode = False
431
-
432
- # Обрабатываем аргументы командной строки
433
- if len(sys.argv) > 1:
434
- if "--no-strict" in sys.argv:
435
- strict_mode = False
436
- print("⚙️ Запуск в нестрогом режиме: команды с ошибками будут зарегистрированы")
437
-
438
- if "--auto-fix" in sys.argv:
439
- auto_fix_mode = True
440
- print("⚙️ Режим автоисправления: типы и описания будут извлечены из аннотаций функций")
441
- else:
442
- print("⚙️ Запуск в строгом режиме: команды с ошибками не будут зарегистрированы")
443
- print(" Используйте --no-strict для отключения строгого режима")
444
- print(" Используйте --auto-fix для автоматического исправления ошибок")
445
-
446
- register_all_commands(strict=strict_mode, auto_fix=auto_fix_mode)
scripts/publish.py DELETED
@@ -1,85 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Скрипт для публикации пакета в PyPI.
4
- """
5
- import sys
6
- import os
7
- import subprocess
8
- import shutil
9
- import argparse
10
-
11
- # Путь к корню проекта
12
- PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
13
-
14
-
15
- def clean_build_dirs():
16
- """Очищает директории сборки."""
17
- print("Очистка директорий сборки...")
18
-
19
- dirs_to_clean = [
20
- os.path.join(PROJECT_ROOT, 'build'),
21
- os.path.join(PROJECT_ROOT, 'dist'),
22
- os.path.join(PROJECT_ROOT, 'mcp_proxy_adapter.egg-info'),
23
- os.path.join(PROJECT_ROOT, 'src/mcp_proxy_adapter.egg-info')
24
- ]
25
-
26
- for dir_path in dirs_to_clean:
27
- if os.path.exists(dir_path):
28
- print(f"Удаление {dir_path}")
29
- shutil.rmtree(dir_path)
30
-
31
-
32
- def build_package():
33
- """Собирает пакет."""
34
- print("Сборка пакета...")
35
-
36
- # Сборка колеса
37
- subprocess.run([sys.executable, "-m", "build", "--wheel", "--sdist"], cwd=PROJECT_ROOT, check=True)
38
-
39
-
40
- def upload_to_pypi(test=True):
41
- """Загружает пакет в PyPI."""
42
- if test:
43
- print("Загрузка в TestPyPI...")
44
- cmd = [
45
- sys.executable, "-m", "twine", "upload",
46
- "--repository-url", "https://test.pypi.org/legacy/",
47
- "dist/*"
48
- ]
49
- else:
50
- print("Загрузка в PyPI...")
51
- cmd = [sys.executable, "-m", "twine", "upload", "dist/*"]
52
-
53
- subprocess.run(cmd, cwd=PROJECT_ROOT, check=True)
54
-
55
-
56
- def main():
57
- parser = argparse.ArgumentParser(description="Публикация пакета в PyPI")
58
- parser.add_argument('--test', action='store_true', help='Загрузить в TestPyPI вместо основного PyPI')
59
- parser.add_argument('--no-clean', action='store_true', help='Не очищать директории сборки')
60
- parser.add_argument('--build-only', action='store_true', help='Только сборка без загрузки')
61
-
62
- args = parser.parse_args()
63
-
64
- try:
65
- if not args.no_clean:
66
- clean_build_dirs()
67
-
68
- build_package()
69
-
70
- if not args.build_only:
71
- upload_to_pypi(test=args.test)
72
-
73
- print("Готово!")
74
- except subprocess.CalledProcessError as e:
75
- print(f"Ошибка при выполнении команды: {e}")
76
- return 1
77
- except Exception as e:
78
- print(f"Ошибка: {e}")
79
- return 1
80
-
81
- return 0
82
-
83
-
84
- if __name__ == "__main__":
85
- sys.exit(main())
tests/conftest.py DELETED
@@ -1,12 +0,0 @@
1
- import warnings
2
- import pytest
3
-
4
- # Отключаем все DeprecationWarning и устаревшие предупреждения для чистоты тестов
5
- warnings.filterwarnings("ignore", category=DeprecationWarning)
6
- warnings.filterwarnings("ignore", category=PendingDeprecationWarning)
7
-
8
- # Для pytest >=7.1 можно использовать mark.filterwarnings
9
- pytestmark = pytest.mark.filterwarnings(
10
- "ignore::DeprecationWarning",
11
- "ignore::PendingDeprecationWarning"
12
- )