mcp-proxy-adapter 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
adapters/__init__.py ADDED
@@ -0,0 +1,16 @@
1
+ """
2
+ Адаптеры Command Registry
3
+ ========================
4
+
5
+ Адаптеры обеспечивают интеграцию Command Registry с различными протоколами и системами.
6
+ Они преобразуют команды из реестра в формат, понятный другим системам.
7
+
8
+ Доступные адаптеры:
9
+ - RESTAdapter: Создает REST API эндпоинты
10
+ - MCPProxyAdapter: Интегрирует с MCPProxy для работы с инструментами моделей ИИ
11
+ """
12
+
13
+ from command_registry.adapters.rest_adapter import RESTAdapter
14
+ from ..adapter import MCPProxyAdapter # Импортируем из основного модуля adapter.py
15
+
16
+ __all__ = ['RESTAdapter', 'MCPProxyAdapter']
analyzers/__init__.py ADDED
@@ -0,0 +1,14 @@
1
+ """
2
+ Analyzers for extracting metadata from functions and docstrings.
3
+
4
+ This module contains classes for analyzing type annotations and docstrings
5
+ of Python functions to automatically extract metadata for commands.
6
+ """
7
+
8
+ from .type_analyzer import TypeAnalyzer
9
+ from .docstring_analyzer import DocstringAnalyzer
10
+
11
+ __all__ = [
12
+ 'TypeAnalyzer',
13
+ 'DocstringAnalyzer',
14
+ ]
@@ -0,0 +1,199 @@
1
+ """
2
+ Docstring analyzer for extracting information from function documentation.
3
+ """
4
+ import inspect
5
+ from typing import Dict, Any, Optional, Callable, List, Tuple
6
+ import docstring_parser
7
+
8
+ class DocstringAnalyzer:
9
+ """
10
+ Docstring analyzer for extracting metadata from function documentation.
11
+
12
+ This class is responsible for analyzing command handler function docstrings
13
+ and extracting function descriptions, parameters, and return values.
14
+ """
15
+
16
+ def analyze(self, handler: Callable) -> Dict[str, Any]:
17
+ """
18
+ Analyzes function docstring and returns metadata.
19
+
20
+ Args:
21
+ handler: Handler function to analyze
22
+
23
+ Returns:
24
+ Dict[str, Any]: Metadata extracted from docstring
25
+ """
26
+ result = {
27
+ "description": "",
28
+ "summary": "",
29
+ "parameters": {},
30
+ "returns": {
31
+ "description": ""
32
+ }
33
+ }
34
+
35
+ # Get function signature
36
+ sig = inspect.signature(handler)
37
+
38
+ # Get docstring
39
+ docstring = handler.__doc__ or ""
40
+
41
+ # Parse docstring
42
+ try:
43
+ parsed_doc = docstring_parser.parse(docstring)
44
+
45
+ # Extract general function description
46
+ if parsed_doc.short_description:
47
+ result["summary"] = parsed_doc.short_description
48
+ result["description"] = parsed_doc.short_description
49
+
50
+ if parsed_doc.long_description:
51
+ # If both short and long descriptions exist, combine them
52
+ if result["description"]:
53
+ result["description"] = f"{result['description']}\n\n{parsed_doc.long_description}"
54
+ else:
55
+ result["description"] = parsed_doc.long_description
56
+
57
+ # Extract parameter information
58
+ for param in parsed_doc.params:
59
+ param_name = param.arg_name
60
+ param_desc = param.description or f"Parameter {param_name}"
61
+ param_type = None
62
+
63
+ # If parameter type is specified in docstring, use it
64
+ if param.type_name:
65
+ param_type = self._parse_type_from_docstring(param.type_name)
66
+
67
+ # Add parameter to metadata
68
+ if param_name not in result["parameters"]:
69
+ result["parameters"][param_name] = {}
70
+
71
+ result["parameters"][param_name]["description"] = param_desc
72
+
73
+ if param_type:
74
+ result["parameters"][param_name]["type"] = param_type
75
+
76
+ # Extract return value information
77
+ if parsed_doc.returns:
78
+ result["returns"]["description"] = parsed_doc.returns.description or "Return value"
79
+
80
+ if parsed_doc.returns.type_name:
81
+ result["returns"]["type"] = self._parse_type_from_docstring(parsed_doc.returns.type_name)
82
+
83
+ except Exception as e:
84
+ # In case of parsing error, use docstring as is
85
+ if docstring:
86
+ result["description"] = docstring.strip()
87
+
88
+ # Fill parameter information from signature if not found in docstring
89
+ for param_name, param in sig.parameters.items():
90
+ # Skip self for methods
91
+ if param_name == 'self':
92
+ continue
93
+
94
+ # If parameter not yet added to metadata, add it
95
+ if param_name not in result["parameters"]:
96
+ result["parameters"][param_name] = {
97
+ "description": f"Parameter {param_name}"
98
+ }
99
+
100
+ # Determine if parameter is required
101
+ required = param.default == inspect.Parameter.empty
102
+ result["parameters"][param_name]["required"] = required
103
+
104
+ # Add default value if exists
105
+ if param.default != inspect.Parameter.empty:
106
+ # Some default values cannot be serialized to JSON
107
+ # So we check if the value can be serialized
108
+ if param.default is None or isinstance(param.default, (str, int, float, bool, list, dict)):
109
+ result["parameters"][param_name]["default"] = param.default
110
+
111
+ return result
112
+
113
+ def validate(self, handler: Callable) -> Tuple[bool, List[str]]:
114
+ """
115
+ Validates that function docstring matches its formal parameters.
116
+
117
+ Args:
118
+ handler: Command handler function
119
+
120
+ Returns:
121
+ Tuple[bool, List[str]]: Validity flag and list of errors
122
+ """
123
+ errors = []
124
+
125
+ # Get function formal parameters
126
+ sig = inspect.signature(handler)
127
+ formal_params = list(sig.parameters.keys())
128
+
129
+ # Skip self parameter for methods
130
+ if formal_params and formal_params[0] == 'self':
131
+ formal_params = formal_params[1:]
132
+
133
+ # Parse docstring
134
+ docstring = handler.__doc__ or ""
135
+ parsed_doc = docstring_parser.parse(docstring)
136
+
137
+ # Check for function description
138
+ if not parsed_doc.short_description and not parsed_doc.long_description:
139
+ errors.append(f"Missing function description")
140
+
141
+ # Get parameters from docstring
142
+ doc_params = {param.arg_name: param for param in parsed_doc.params}
143
+
144
+ # Check that all formal parameters are described in docstring
145
+ for param in formal_params:
146
+ if param not in doc_params and param != 'params': # 'params' is special case, can be dictionary of all parameters
147
+ errors.append(f"Parameter '{param}' not described in function docstring")
148
+
149
+ # Check for returns in docstring
150
+ if not parsed_doc.returns and not any(t.type_name == 'Returns' for t in parsed_doc.meta):
151
+ errors.append(f"Missing return value description in function docstring")
152
+
153
+ return len(errors) == 0, errors
154
+
155
+ def _parse_type_from_docstring(self, type_str: str) -> str:
156
+ """
157
+ Parses type from string representation in docstring.
158
+
159
+ Args:
160
+ type_str: String representation of type
161
+
162
+ Returns:
163
+ str: Type in OpenAPI format
164
+ """
165
+ # Simple mapping of string types to OpenAPI types
166
+ type_map = {
167
+ "str": "string",
168
+ "string": "string",
169
+ "int": "integer",
170
+ "integer": "integer",
171
+ "float": "number",
172
+ "number": "number",
173
+ "bool": "boolean",
174
+ "boolean": "boolean",
175
+ "list": "array",
176
+ "array": "array",
177
+ "dict": "object",
178
+ "object": "object",
179
+ "none": "null",
180
+ "null": "null",
181
+ }
182
+
183
+ # Convert to lowercase and remove spaces
184
+ cleaned_type = type_str.lower().strip()
185
+
186
+ # Check for simple types
187
+ if cleaned_type in type_map:
188
+ return type_map[cleaned_type]
189
+
190
+ # Check for List[X]
191
+ if cleaned_type.startswith("list[") or cleaned_type.startswith("array["):
192
+ return "array"
193
+
194
+ # Check for Dict[X, Y]
195
+ if cleaned_type.startswith("dict[") or cleaned_type.startswith("object["):
196
+ return "object"
197
+
198
+ # Default to object
199
+ return "object"
@@ -0,0 +1,151 @@
1
+ """
2
+ Type analyzer for extracting information from function type annotations.
3
+ """
4
+ import inspect
5
+ from typing import Dict, Any, List, Optional, Callable, Union, get_origin, get_args, get_type_hints
6
+
7
+ class TypeAnalyzer:
8
+ """
9
+ Type analyzer for extracting information from function type annotations.
10
+
11
+ This class is responsible for analyzing type annotations of command handler functions
12
+ and converting them to JSON Schema/OpenAPI type format.
13
+ """
14
+
15
+ def __init__(self):
16
+ # Mapping Python types to OpenAPI types
17
+ self.type_map = {
18
+ str: "string",
19
+ int: "integer",
20
+ float: "number",
21
+ bool: "boolean",
22
+ list: "array",
23
+ dict: "object",
24
+ Any: "object",
25
+ None: "null",
26
+ }
27
+
28
+ def analyze(self, handler: Callable) -> Dict[str, Any]:
29
+ """
30
+ Analyzes function type annotations and returns metadata.
31
+
32
+ Args:
33
+ handler: Handler function to analyze
34
+
35
+ Returns:
36
+ Dict[str, Any]: Metadata about parameter types and return value
37
+ """
38
+ result = {
39
+ "parameters": {},
40
+ "returns": None
41
+ }
42
+
43
+ # Get function signature
44
+ sig = inspect.signature(handler)
45
+
46
+ # Get type annotations
47
+ type_hints = self._get_type_hints(handler)
48
+
49
+ # Analyze parameters
50
+ for param_name, param in sig.parameters.items():
51
+ # Skip self for methods
52
+ if param_name == 'self':
53
+ continue
54
+
55
+ # If parameter is named params, assume it's a dictionary of all parameters
56
+ if param_name == 'params':
57
+ continue
58
+
59
+ # Determine if parameter is required
60
+ required = param.default == inspect.Parameter.empty
61
+
62
+ # Determine parameter type
63
+ param_type = "object" # Default type
64
+
65
+ if param_name in type_hints:
66
+ param_type = self._map_type_to_openapi(type_hints[param_name])
67
+
68
+ # Create parameter metadata
69
+ param_metadata = {
70
+ "type": param_type,
71
+ "required": required
72
+ }
73
+
74
+ # Add default value if exists
75
+ if param.default != inspect.Parameter.empty:
76
+ # Some default values cannot be serialized to JSON
77
+ # So we convert them to string representation for such cases
78
+ if param.default is None or isinstance(param.default, (str, int, float, bool, list, dict)):
79
+ param_metadata["default"] = param.default
80
+
81
+ # Add parameter to metadata
82
+ result["parameters"][param_name] = param_metadata
83
+
84
+ # Analyze return value
85
+ if 'return' in type_hints:
86
+ result["returns"] = self._map_type_to_openapi(type_hints['return'])
87
+
88
+ return result
89
+
90
+ def _get_type_hints(self, handler: Callable) -> Dict[str, Any]:
91
+ """
92
+ Gets type annotations of a function.
93
+
94
+ Args:
95
+ handler: Handler function
96
+
97
+ Returns:
98
+ Dict[str, Any]: Type annotations
99
+ """
100
+ try:
101
+ return get_type_hints(handler)
102
+ except Exception:
103
+ # If failed to get annotations via get_type_hints,
104
+ # extract them manually from __annotations__
105
+ return getattr(handler, "__annotations__", {})
106
+
107
+ def _map_type_to_openapi(self, type_hint: Any) -> Union[str, Dict[str, Any]]:
108
+ """
109
+ Converts Python type to OpenAPI type.
110
+
111
+ Args:
112
+ type_hint: Python type
113
+
114
+ Returns:
115
+ Union[str, Dict[str, Any]]: OpenAPI type string representation or schema
116
+ """
117
+ # Check for None
118
+ if type_hint is None:
119
+ return "null"
120
+
121
+ # Handle primitive types
122
+ if type_hint in self.type_map:
123
+ return self.type_map[type_hint]
124
+
125
+ # Check for generic types
126
+ origin = get_origin(type_hint)
127
+ if origin is not None:
128
+ # Handle List[X], Dict[X, Y], etc.
129
+ if origin in (list, List):
130
+ args = get_args(type_hint)
131
+ if args:
132
+ item_type = self._map_type_to_openapi(args[0])
133
+ return {
134
+ "type": "array",
135
+ "items": item_type if isinstance(item_type, dict) else {"type": item_type}
136
+ }
137
+ return "array"
138
+ elif origin in (dict, Dict):
139
+ # For dict we just return object, as OpenAPI
140
+ # doesn't have a direct equivalent for Dict[X, Y]
141
+ return "object"
142
+ elif origin is Union:
143
+ # For Union we take the first type that is not None
144
+ args = get_args(type_hint)
145
+ for arg in args:
146
+ if arg is not type(None):
147
+ return self._map_type_to_openapi(arg)
148
+ return "object"
149
+
150
+ # Default to object
151
+ return "object"
cli/__init__.py ADDED
@@ -0,0 +1,12 @@
1
+ """
2
+ Командный интерфейс для запуска команд из командной строки.
3
+
4
+ Этот модуль предоставляет инструменты для создания командной строки
5
+ на основе зарегистрированных команд.
6
+ """
7
+
8
+ from command_registry.cli.command_runner import CommandRunner
9
+
10
+ __all__ = [
11
+ 'CommandRunner',
12
+ ]
cli/__main__.py ADDED
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env python
2
+ """
3
+ Entry point for the command registry CLI interface.
4
+
5
+ This module allows running commands from the command line
6
+ using the command dispatcher.
7
+
8
+ Usage:
9
+ python -m command_registry.cli [command] [args...]
10
+ """
11
+
12
+ import os
13
+ import sys
14
+ import importlib.util
15
+ from typing import Optional
16
+
17
+ from command_registry.cli.command_runner import CommandRunner
18
+ from command_registry.dispatchers.base_dispatcher import BaseDispatcher
19
+ from command_registry.dispatchers.command_dispatcher import CommandDispatcher
20
+
21
+
22
+ def find_dispatcher() -> BaseDispatcher:
23
+ """
24
+ Finds and creates command dispatcher.
25
+
26
+ Search order:
27
+ 1. Checks DISPATCHER_MODULE environment variable
28
+ 2. Looks for app.py in current directory
29
+ 3. Creates new CommandDispatcher
30
+
31
+ Returns:
32
+ BaseDispatcher: Command dispatcher
33
+ """
34
+ # Check environment variable
35
+ dispatcher_module = os.environ.get("DISPATCHER_MODULE")
36
+ if dispatcher_module:
37
+ try:
38
+ module_path, attr_name = dispatcher_module.rsplit(":", 1)
39
+ module = importlib.import_module(module_path)
40
+ return getattr(module, attr_name)
41
+ except (ValueError, ImportError, AttributeError) as e:
42
+ print(f"Failed to load dispatcher from {dispatcher_module}: {e}",
43
+ file=sys.stderr)
44
+
45
+ # Check app.py file
46
+ app_path = os.path.join(os.getcwd(), "app.py")
47
+ if os.path.exists(app_path):
48
+ try:
49
+ spec = importlib.util.spec_from_file_location("app", app_path)
50
+ if spec and spec.loader:
51
+ app = importlib.util.module_from_spec(spec)
52
+ spec.loader.exec_module(app)
53
+
54
+ # Look for dispatcher in module
55
+ dispatcher = getattr(app, "dispatcher", None)
56
+ if dispatcher and isinstance(dispatcher, BaseDispatcher):
57
+ return dispatcher
58
+ except Exception as e:
59
+ print(f"Failed to load dispatcher from app.py: {e}",
60
+ file=sys.stderr)
61
+
62
+ # Create new dispatcher if not found
63
+ return CommandDispatcher()
64
+
65
+
66
+ def main() -> None:
67
+ """
68
+ Main function for running CLI interface.
69
+ """
70
+ # Get dispatcher
71
+ dispatcher = find_dispatcher()
72
+
73
+ # Create and run CommandRunner
74
+ runner = CommandRunner(dispatcher)
75
+ runner.run(sys.argv[1:])
76
+
77
+
78
+ if __name__ == "__main__":
79
+ main()