mcp-proxy-adapter 2.1.2__py3-none-any.whl → 2.1.4__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. {examples → mcp_proxy_adapter/examples}/openapi_server.py +35 -7
  2. {mcp_proxy_adapter-2.1.2.dist-info → mcp_proxy_adapter-2.1.4.dist-info}/METADATA +98 -2
  3. mcp_proxy_adapter-2.1.4.dist-info/RECORD +28 -0
  4. mcp_proxy_adapter-2.1.4.dist-info/top_level.txt +1 -0
  5. docs/README.md +0 -172
  6. docs/README_ru.md +0 -172
  7. docs/architecture.md +0 -251
  8. docs/architecture_ru.md +0 -343
  9. docs/command_development.md +0 -250
  10. docs/command_development_ru.md +0 -593
  11. docs/deployment.md +0 -251
  12. docs/deployment_ru.md +0 -1298
  13. docs/examples.md +0 -254
  14. docs/examples_ru.md +0 -401
  15. docs/mcp_proxy_adapter.md +0 -251
  16. docs/mcp_proxy_adapter_ru.md +0 -405
  17. docs/quickstart.md +0 -251
  18. docs/quickstart_ru.md +0 -397
  19. docs/testing.md +0 -255
  20. docs/testing_ru.md +0 -469
  21. docs/validation_ru.md +0 -287
  22. examples/mcp_proxy_config.json +0 -175
  23. mcp_proxy_adapter-2.1.2.dist-info/RECORD +0 -61
  24. mcp_proxy_adapter-2.1.2.dist-info/top_level.txt +0 -5
  25. scripts/code_analyzer/code_analyzer.py +0 -328
  26. scripts/code_analyzer/register_commands.py +0 -446
  27. scripts/publish.py +0 -85
  28. tests/conftest.py +0 -12
  29. tests/test_adapter.py +0 -529
  30. tests/test_adapter_coverage.py +0 -274
  31. tests/test_basic_dispatcher.py +0 -169
  32. tests/test_command_registry.py +0 -328
  33. tests/test_examples.py +0 -32
  34. tests/test_mcp_proxy_adapter.py +0 -568
  35. tests/test_mcp_proxy_adapter_basic.py +0 -262
  36. tests/test_part1.py +0 -348
  37. tests/test_part2.py +0 -524
  38. tests/test_schema.py +0 -358
  39. tests/test_simple_adapter.py +0 -251
  40. {examples → mcp_proxy_adapter/examples}/analyze_config.py +0 -0
  41. {examples → mcp_proxy_adapter/examples}/basic_integration.py +0 -0
  42. {examples → mcp_proxy_adapter/examples}/docstring_and_schema_example.py +0 -0
  43. {examples → mcp_proxy_adapter/examples}/extension_example.py +0 -0
  44. {examples → mcp_proxy_adapter/examples}/help_best_practices.py +0 -0
  45. {examples → mcp_proxy_adapter/examples}/help_usage.py +0 -0
  46. {examples → mcp_proxy_adapter/examples}/mcp_proxy_client.py +0 -0
  47. {examples → mcp_proxy_adapter/examples}/project_structure_example.py +0 -0
  48. {examples → mcp_proxy_adapter/examples}/testing_example.py +0 -0
  49. {mcp_proxy_adapter-2.1.2.dist-info → mcp_proxy_adapter-2.1.4.dist-info}/WHEEL +0 -0
  50. {mcp_proxy_adapter-2.1.2.dist-info → mcp_proxy_adapter-2.1.4.dist-info}/licenses/LICENSE +0 -0
@@ -1,593 +0,0 @@
1
- # Руководство по разработке команд
2
-
3
- В этом руководстве описаны лучшие практики и рекомендации по разработке команд для системы Command Registry.
4
-
5
- ## Основные принципы
6
-
7
- При разработке команд для Command Registry придерживайтесь следующих принципов:
8
-
9
- 1. **Единственная ответственность**: команда должна выполнять одну задачу и делать это хорошо
10
- 2. **Декларативность**: команда должна быть самодокументированной через типизацию и docstrings
11
- 3. **Независимость**: команда не должна зависеть от других команд
12
- 4. **Идемпотентность**: повторное выполнение команды с одинаковыми параметрами должно давать идентичный результат
13
- 5. **Валидация**: команда должна проверять входные данные перед выполнением
14
-
15
- ## Структура команды
16
-
17
- Рекомендуемая структура команды:
18
-
19
- ```python
20
- from typing import Dict, List, Optional, Any, Union, Tuple
21
- import logging
22
-
23
- logger = logging.getLogger(__name__)
24
-
25
- def command_name(
26
- required_param: str,
27
- optional_param: Optional[int] = None,
28
- *,
29
- keyword_only_param: bool = False
30
- ) -> Dict[str, Any]:
31
- """
32
- Краткое описание команды (одно предложение).
33
-
34
- Подробное описание команды, объясняющее её назначение,
35
- особенности работы и возможные побочные эффекты.
36
-
37
- Args:
38
- required_param: Описание обязательного параметра
39
- optional_param: Описание необязательного параметра
40
- keyword_only_param: Описание параметра, который можно передать только по имени
41
-
42
- Returns:
43
- Описание возвращаемого значения
44
-
45
- Raises:
46
- ValueError: Когда возникает ошибка валидации
47
- RuntimeError: Когда возникает ошибка выполнения
48
-
49
- Examples:
50
- >>> command_name("value", 42, keyword_only_param=True)
51
- {'status': 'success', 'result': 'value_processed'}
52
- """
53
- # Логирование начала выполнения
54
- logger.debug(f"Executing command_name with params: {required_param}, {optional_param}, {keyword_only_param}")
55
-
56
- # Валидация параметров
57
- if not required_param:
58
- raise ValueError("required_param cannot be empty")
59
-
60
- if optional_param is not None and optional_param < 0:
61
- raise ValueError("optional_param must be non-negative")
62
-
63
- # Выполнение основной логики
64
- try:
65
- # Основная логика команды
66
- result = process_data(required_param, optional_param, keyword_only_param)
67
-
68
- # Логирование успешного выполнения
69
- logger.info(f"Command command_name executed successfully with result: {result}")
70
-
71
- return {
72
- "status": "success",
73
- "result": result
74
- }
75
- except Exception as e:
76
- # Логирование ошибки
77
- logger.error(f"Error executing command command_name: {str(e)}", exc_info=True)
78
-
79
- # Проброс исключения для обработки выше
80
- raise RuntimeError(f"Failed to execute command: {str(e)}") from e
81
- ```
82
-
83
- ## Лучшие практики
84
-
85
- ### Типизация
86
-
87
- 1. **Используйте типизацию для всех параметров и возвращаемого значения**:
88
-
89
- ```python
90
- def process_data(data: List[Dict[str, Any]], limit: Optional[int] = None) -> Tuple[List[Dict[str, Any]], int]:
91
- # ...
92
- ```
93
-
94
- 2. **Используйте сложные типы из модуля `typing`**:
95
-
96
- ```python
97
- from typing import Dict, List, Optional, Union, Callable, TypeVar, Generic
98
-
99
- T = TypeVar('T')
100
-
101
- def filter_items(
102
- items: List[T],
103
- predicate: Callable[[T], bool]
104
- ) -> List[T]:
105
- # ...
106
- ```
107
-
108
- 3. **Определяйте свои типы для сложных структур**:
109
-
110
- ```python
111
- from typing import Dict, List, TypedDict, Optional
112
-
113
- class UserData(TypedDict):
114
- id: str
115
- name: str
116
- email: str
117
- roles: List[str]
118
- profile: Optional[Dict[str, str]]
119
-
120
- def get_user(user_id: str) -> UserData:
121
- # ...
122
- ```
123
-
124
- ### Документация
125
-
126
- 1. **Используйте docstrings для описания команды, её параметров и возвращаемого значения**:
127
-
128
- ```python
129
- def calculate_total(prices: List[float], discount: float = 0.0) -> float:
130
- """
131
- Рассчитывает общую стоимость с учетом скидки.
132
-
133
- Args:
134
- prices: Список цен товаров
135
- discount: Скидка в процентах (от 0.0 до 1.0)
136
-
137
- Returns:
138
- Общая стоимость со скидкой
139
-
140
- Raises:
141
- ValueError: Если скидка не в диапазоне [0, 1]
142
- """
143
- ```
144
-
145
- 2. **Добавляйте примеры использования в docstring**:
146
-
147
- ```python
148
- def calculate_total(prices: List[float], discount: float = 0.0) -> float:
149
- """
150
- ...
151
-
152
- Examples:
153
- >>> calculate_total([10.0, 20.0, 30.0])
154
- 60.0
155
- >>> calculate_total([10.0, 20.0, 30.0], discount=0.1)
156
- 54.0
157
- """
158
- ```
159
-
160
- ### Валидация параметров
161
-
162
- 1. **Проверяйте корректность параметров в начале функции**:
163
-
164
- ```python
165
- def update_user(user_id: str, name: Optional[str] = None, email: Optional[str] = None) -> Dict[str, Any]:
166
- """Обновляет информацию о пользователе."""
167
- if not user_id:
168
- raise ValueError("user_id cannot be empty")
169
-
170
- if email is not None and "@" not in email:
171
- raise ValueError("Invalid email format")
172
-
173
- # Основная логика...
174
- ```
175
-
176
- 2. **Используйте библиотеки для валидации сложных данных**:
177
-
178
- ```python
179
- from pydantic import BaseModel, EmailStr, validator
180
-
181
- class UserUpdateParams(BaseModel):
182
- user_id: str
183
- name: Optional[str] = None
184
- email: Optional[EmailStr] = None
185
-
186
- @validator('user_id')
187
- def user_id_must_not_be_empty(cls, v):
188
- if not v:
189
- raise ValueError('user_id cannot be empty')
190
- return v
191
-
192
- def update_user(params: UserUpdateParams) -> Dict[str, Any]:
193
- # Pydantic уже выполнил валидацию
194
- # Основная логика...
195
- ```
196
-
197
- ### Обработка ошибок
198
-
199
- 1. **Используйте специфичные типы исключений**:
200
-
201
- ```python
202
- def get_user(user_id: str) -> Dict[str, Any]:
203
- if not user_id:
204
- raise ValueError("user_id cannot be empty")
205
-
206
- user = db.find_user(user_id)
207
- if user is None:
208
- raise UserNotFoundError(f"User with id {user_id} not found")
209
-
210
- return user
211
- ```
212
-
213
- 2. **Сохраняйте контекст ошибки с помощью цепочки исключений**:
214
-
215
- ```python
216
- def process_order(order_id: str) -> Dict[str, Any]:
217
- try:
218
- order = get_order(order_id)
219
- # Обработка заказа...
220
- except OrderNotFoundError as e:
221
- raise ProcessingError(f"Failed to process order {order_id}") from e
222
- except Exception as e:
223
- raise ProcessingError(f"Unexpected error while processing order {order_id}") from e
224
- ```
225
-
226
- ### Логирование
227
-
228
- 1. **Используйте логирование для отслеживания хода выполнения команды**:
229
-
230
- ```python
231
- import logging
232
-
233
- logger = logging.getLogger(__name__)
234
-
235
- def complex_operation(data: Dict[str, Any]) -> Dict[str, Any]:
236
- logger.info(f"Starting complex operation with data: {data}")
237
-
238
- try:
239
- # Шаг 1
240
- logger.debug("Performing step 1")
241
- result_1 = step_1(data)
242
-
243
- # Шаг 2
244
- logger.debug("Performing step 2")
245
- result_2 = step_2(result_1)
246
-
247
- # Финальный шаг
248
- logger.debug("Performing final step")
249
- final_result = final_step(result_2)
250
-
251
- logger.info(f"Complex operation completed successfully with result: {final_result}")
252
- return final_result
253
- except Exception as e:
254
- logger.error(f"Error during complex operation: {str(e)}", exc_info=True)
255
- raise
256
- ```
257
-
258
- ### Тестирование команд
259
-
260
- 1. **Пишите юнит-тесты для команд**:
261
-
262
- ```python
263
- import pytest
264
- from myapp.commands import calculate_total
265
-
266
- def test_calculate_total_basic():
267
- result = calculate_total([10.0, 20.0, 30.0])
268
- assert result == 60.0
269
-
270
- def test_calculate_total_with_discount():
271
- result = calculate_total([10.0, 20.0, 30.0], discount=0.1)
272
- assert result == 54.0
273
-
274
- def test_calculate_total_empty_list():
275
- result = calculate_total([])
276
- assert result == 0.0
277
-
278
- def test_calculate_total_invalid_discount():
279
- with pytest.raises(ValueError):
280
- calculate_total([10.0, 20.0], discount=1.5)
281
- ```
282
-
283
- 2. **Используйте параметризированные тесты**:
284
-
285
- ```python
286
- import pytest
287
- from myapp.commands import calculate_total
288
-
289
- @pytest.mark.parametrize("prices, discount, expected", [
290
- ([10.0, 20.0, 30.0], 0.0, 60.0),
291
- ([10.0, 20.0, 30.0], 0.1, 54.0),
292
- ([10.0], 0.5, 5.0),
293
- ([], 0.0, 0.0),
294
- ])
295
- def test_calculate_total(prices, discount, expected):
296
- result = calculate_total(prices, discount)
297
- assert result == expected
298
-
299
- @pytest.mark.parametrize("invalid_discount", [-0.1, 1.1, 2.0])
300
- def test_calculate_total_invalid_discount(invalid_discount):
301
- with pytest.raises(ValueError):
302
- calculate_total([10.0], discount=invalid_discount)
303
- ```
304
-
305
- ## Организация команд
306
-
307
- ### Группировка по модулям
308
-
309
- Для больших проектов рекомендуется группировать команды по функциональности:
310
-
311
- ```
312
- commands/
313
- __init__.py
314
- users/
315
- __init__.py
316
- create.py
317
- update.py
318
- delete.py
319
- orders/
320
- __init__.py
321
- create.py
322
- process.py
323
- cancel.py
324
- products/
325
- __init__.py
326
- ...
327
- ```
328
-
329
- В файле `commands/__init__.py` можно экспортировать все команды:
330
-
331
- ```python
332
- # commands/__init__.py
333
- from .users.create import create_user
334
- from .users.update import update_user
335
- from .users.delete import delete_user
336
- from .orders.create import create_order
337
- from .orders.process import process_order
338
- from .orders.cancel import cancel_order
339
- # ...
340
-
341
- __all__ = [
342
- 'create_user',
343
- 'update_user',
344
- 'delete_user',
345
- 'create_order',
346
- 'process_order',
347
- 'cancel_order',
348
- # ...
349
- ]
350
- ```
351
-
352
- ### Регистрация команд
353
-
354
- При большом количестве команд рекомендуется использовать автоматическую регистрацию:
355
-
356
- ```python
357
- from command_registry import CommandRegistry
358
- from command_registry.dispatchers import CommandDispatcher
359
- import commands
360
-
361
- dispatcher = CommandDispatcher()
362
- registry = CommandRegistry(dispatcher)
363
-
364
- # Регистрация всех команд из модуля
365
- registry.scan_module(commands, recursive=True)
366
- ```
367
-
368
- ## Примеры команд
369
-
370
- ### Простая команда
371
-
372
- ```python
373
- def add_numbers(a: int, b: int) -> int:
374
- """
375
- Складывает два числа.
376
-
377
- Args:
378
- a: Первое число
379
- b: Второе число
380
-
381
- Returns:
382
- Сумма чисел
383
- """
384
- return a + b
385
- ```
386
-
387
- ### Команда с валидацией
388
-
389
- ```python
390
- def calculate_discount(price: float, percentage: float) -> float:
391
- """
392
- Рассчитывает сумму скидки.
393
-
394
- Args:
395
- price: Исходная цена (должна быть положительной)
396
- percentage: Процент скидки (от 0 до 100)
397
-
398
- Returns:
399
- Сумма скидки
400
-
401
- Raises:
402
- ValueError: Если цена отрицательная или процент скидки вне диапазона [0, 100]
403
- """
404
- if price < 0:
405
- raise ValueError("Price cannot be negative")
406
-
407
- if not 0 <= percentage <= 100:
408
- raise ValueError("Percentage must be between 0 and 100")
409
-
410
- return price * (percentage / 100)
411
- ```
412
-
413
- ### Команда с внешними зависимостями
414
-
415
- ```python
416
- import logging
417
- from typing import Dict, Any, Optional
418
- from database import get_database_connection
419
-
420
- logger = logging.getLogger(__name__)
421
-
422
- def get_user_profile(user_id: str, include_private: bool = False) -> Dict[str, Any]:
423
- """
424
- Получает профиль пользователя из базы данных.
425
-
426
- Args:
427
- user_id: Идентификатор пользователя
428
- include_private: Включать ли приватные данные
429
-
430
- Returns:
431
- Профиль пользователя
432
-
433
- Raises:
434
- ValueError: Если идентификатор пользователя пустой
435
- UserNotFoundError: Если пользователь не найден
436
- DatabaseError: При ошибке работы с базой данных
437
- """
438
- if not user_id:
439
- raise ValueError("user_id cannot be empty")
440
-
441
- logger.info(f"Retrieving user profile for user_id={user_id}")
442
-
443
- try:
444
- db = get_database_connection()
445
- user = db.users.find_one({"_id": user_id})
446
-
447
- if user is None:
448
- raise UserNotFoundError(f"User with id {user_id} not found")
449
-
450
- # Формирование результата
451
- profile = {
452
- "id": user["_id"],
453
- "username": user["username"],
454
- "name": user["name"],
455
- "created_at": user["created_at"].isoformat()
456
- }
457
-
458
- # Добавление приватных данных, если требуется
459
- if include_private:
460
- profile.update({
461
- "email": user["email"],
462
- "phone": user["phone"],
463
- "last_login": user.get("last_login", {}).get("timestamp", "").isoformat()
464
- })
465
-
466
- logger.info(f"Successfully retrieved user profile for user_id={user_id}")
467
- return profile
468
-
469
- except UserNotFoundError:
470
- logger.warning(f"User with id {user_id} not found")
471
- raise
472
- except Exception as e:
473
- logger.error(f"Error retrieving user profile for user_id={user_id}: {str(e)}", exc_info=True)
474
- raise DatabaseError(f"Failed to retrieve user profile: {str(e)}") from e
475
- ```
476
-
477
- ### Асинхронная команда
478
-
479
- ```python
480
- import logging
481
- import asyncio
482
- from typing import Dict, Any, List
483
- from database import get_async_database_connection
484
-
485
- logger = logging.getLogger(__name__)
486
-
487
- async def search_products(
488
- query: str,
489
- categories: Optional[List[str]] = None,
490
- min_price: Optional[float] = None,
491
- max_price: Optional[float] = None,
492
- limit: int = 20,
493
- offset: int = 0
494
- ) -> Dict[str, Any]:
495
- """
496
- Выполняет поиск товаров по различным критериям.
497
-
498
- Args:
499
- query: Поисковый запрос
500
- categories: Список категорий для фильтрации
501
- min_price: Минимальная цена
502
- max_price: Максимальная цена
503
- limit: Максимальное количество результатов
504
- offset: Смещение результатов для пагинации
505
-
506
- Returns:
507
- Словарь с результатами поиска и метаданными
508
- """
509
- logger.info(f"Searching products with query: {query}, categories: {categories}, "
510
- f"price range: {min_price}-{max_price}, limit: {limit}, offset: {offset}")
511
-
512
- # Формирование фильтра
513
- filter_query = {"$text": {"$search": query}}
514
-
515
- if categories:
516
- filter_query["category"] = {"$in": categories}
517
-
518
- price_filter = {}
519
- if min_price is not None:
520
- price_filter["$gte"] = min_price
521
- if max_price is not None:
522
- price_filter["$lte"] = max_price
523
-
524
- if price_filter:
525
- filter_query["price"] = price_filter
526
-
527
- try:
528
- # Получение соединения с базой данных
529
- db = await get_async_database_connection()
530
-
531
- # Выполнение запроса
532
- cursor = db.products.find(filter_query).sort("relevance", -1).skip(offset).limit(limit)
533
- products = await cursor.to_list(length=limit)
534
-
535
- # Получение общего количества результатов
536
- total_count = await db.products.count_documents(filter_query)
537
-
538
- # Формирование результата
539
- result = {
540
- "items": products,
541
- "total": total_count,
542
- "limit": limit,
543
- "offset": offset,
544
- "has_more": offset + len(products) < total_count
545
- }
546
-
547
- logger.info(f"Found {total_count} products for query: {query}")
548
- return result
549
-
550
- except Exception as e:
551
- logger.error(f"Error searching products: {str(e)}", exc_info=True)
552
- raise SearchError(f"Failed to search products: {str(e)}") from e
553
- ```
554
-
555
- ## Рекомендации по именованию команд
556
-
557
- 1. **Используйте глаголы для обозначения действий**:
558
- - `create_user`
559
- - `update_profile`
560
- - `delete_item`
561
- - `process_payment`
562
-
563
- 2. **Будьте консистентны в именовании**:
564
- - `get_user`, `get_order`, `get_product` (не `fetch_user`, `retrieve_order`)
565
- - `create_user`, `create_order`, `create_product` (не `add_user`, `new_order`)
566
-
567
- 3. **Избегайте сокращений и аббревиатур**:
568
- - `calculate_total` вместо `calc_total`
569
- - `verify_email` вместо `ver_email`
570
-
571
- 4. **Используйте snake_case для имен команд**:
572
- - `process_payment` вместо `processPayment`
573
- - `update_user_profile` вместо `updateUserProfile`
574
-
575
- 5. **Включайте в название объект, над которым выполняется действие**:
576
- - `create_user` вместо просто `create`
577
- - `send_notification` вместо просто `send`
578
-
579
- ## Совместимость команд
580
-
581
- При разработке и изменении команд соблюдайте правила совместимости:
582
-
583
- 1. **Не меняйте тип возвращаемого значения**
584
- 2. **Не удаляйте существующие параметры**
585
- 3. **Добавляйте только необязательные параметры**
586
- 4. **Не меняйте семантику параметров**
587
- 5. **Не изменяйте логику работы команды без изменения её названия**
588
-
589
- Если требуется несовместимое изменение:
590
-
591
- 1. Создайте новую команду с другим именем
592
- 2. Пометьте старую команду как устаревшую (deprecated)
593
- 3. В документации укажите рекомендацию по миграции