mcp-proxy-adapter 2.1.13__py3-none-any.whl → 2.1.14__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.
- mcp_proxy_adapter/adapter.py +697 -0
- mcp_proxy_adapter/analyzers/docstring_analyzer.py +199 -0
- mcp_proxy_adapter/analyzers/type_analyzer.py +151 -0
- mcp_proxy_adapter/dispatchers/base_dispatcher.py +85 -0
- mcp_proxy_adapter/dispatchers/json_rpc_dispatcher.py +4 -3
- mcp_proxy_adapter/examples/docstring_and_schema_example.py +11 -2
- mcp_proxy_adapter/examples/extension_example.py +17 -5
- mcp_proxy_adapter/examples/testing_example.py +12 -1
- mcp_proxy_adapter/models.py +47 -0
- mcp_proxy_adapter/registry.py +439 -0
- mcp_proxy_adapter/schema.py +257 -0
- mcp_proxy_adapter/validators/docstring_validator.py +75 -0
- mcp_proxy_adapter/validators/metadata_validator.py +76 -0
- {mcp_proxy_adapter-2.1.13.dist-info → mcp_proxy_adapter-2.1.14.dist-info}/METADATA +1 -1
- mcp_proxy_adapter-2.1.14.dist-info/RECORD +28 -0
- mcp_proxy_adapter-2.1.13.dist-info/RECORD +0 -19
- {mcp_proxy_adapter-2.1.13.dist-info → mcp_proxy_adapter-2.1.14.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-2.1.13.dist-info → mcp_proxy_adapter-2.1.14.dist-info}/licenses/LICENSE +0 -0
- {mcp_proxy_adapter-2.1.13.dist-info → mcp_proxy_adapter-2.1.14.dist-info}/top_level.txt +0 -0
@@ -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"
|
@@ -0,0 +1,85 @@
|
|
1
|
+
"""
|
2
|
+
Base command dispatcher class.
|
3
|
+
"""
|
4
|
+
from abc import ABC, abstractmethod
|
5
|
+
from typing import Dict, Any, Callable, List, Optional
|
6
|
+
|
7
|
+
class BaseDispatcher(ABC):
|
8
|
+
"""
|
9
|
+
Abstract base class for command dispatchers.
|
10
|
+
|
11
|
+
Defines the interface that all command dispatchers must implement.
|
12
|
+
Dispatchers are responsible for registering and executing commands.
|
13
|
+
"""
|
14
|
+
|
15
|
+
@abstractmethod
|
16
|
+
def register_handler(
|
17
|
+
self,
|
18
|
+
command: str,
|
19
|
+
handler: Callable,
|
20
|
+
description: str = "",
|
21
|
+
summary: str = "",
|
22
|
+
params: Dict[str, Any] = None
|
23
|
+
) -> None:
|
24
|
+
"""
|
25
|
+
Registers a command handler.
|
26
|
+
|
27
|
+
Args:
|
28
|
+
command: Command name
|
29
|
+
handler: Command handler function
|
30
|
+
description: Command description
|
31
|
+
summary: Brief command summary
|
32
|
+
params: Command parameters description
|
33
|
+
"""
|
34
|
+
pass
|
35
|
+
|
36
|
+
@abstractmethod
|
37
|
+
def execute(self, command: str, **kwargs) -> Any:
|
38
|
+
"""
|
39
|
+
Executes a command with the specified parameters.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
command: Command name
|
43
|
+
**kwargs: Command parameters
|
44
|
+
|
45
|
+
Returns:
|
46
|
+
Any: Command execution result
|
47
|
+
|
48
|
+
Raises:
|
49
|
+
CommandNotFoundError: If command is not found
|
50
|
+
CommandExecutionError: On command execution error
|
51
|
+
"""
|
52
|
+
pass
|
53
|
+
|
54
|
+
@abstractmethod
|
55
|
+
def get_valid_commands(self) -> List[str]:
|
56
|
+
"""
|
57
|
+
Returns a list of all registered command names.
|
58
|
+
|
59
|
+
Returns:
|
60
|
+
List[str]: List of command names
|
61
|
+
"""
|
62
|
+
pass
|
63
|
+
|
64
|
+
@abstractmethod
|
65
|
+
def get_command_info(self, command: str) -> Optional[Dict[str, Any]]:
|
66
|
+
"""
|
67
|
+
Returns information about a command.
|
68
|
+
|
69
|
+
Args:
|
70
|
+
command: Command name
|
71
|
+
|
72
|
+
Returns:
|
73
|
+
Optional[Dict[str, Any]]: Command information or None if command not found
|
74
|
+
"""
|
75
|
+
pass
|
76
|
+
|
77
|
+
@abstractmethod
|
78
|
+
def get_commands_info(self) -> Dict[str, Dict[str, Any]]:
|
79
|
+
"""
|
80
|
+
Returns information about all registered commands.
|
81
|
+
|
82
|
+
Returns:
|
83
|
+
Dict[str, Dict[str, Any]]: Dictionary {command_name: information}
|
84
|
+
"""
|
85
|
+
pass
|
@@ -145,13 +145,14 @@ class JsonRpcDispatcher(BaseDispatcher):
|
|
145
145
|
raise e
|
146
146
|
|
147
147
|
async def execute(self, command: str, **kwargs) -> Any:
|
148
|
-
|
148
|
+
"""
|
149
|
+
Executes a command with the specified parameters.
|
150
|
+
"""
|
149
151
|
if command not in self._handlers:
|
150
152
|
raise CommandNotFoundError(f"Command '{command}' not found")
|
151
153
|
handler = self._handlers[command]
|
152
154
|
try:
|
153
|
-
|
154
|
-
return result
|
155
|
+
return await self._call_handler_always_awaitable(handler, kwargs)
|
155
156
|
except Exception as e:
|
156
157
|
logger.error(f"Error executing command '{command}': {str(e)}")
|
157
158
|
logger.debug(traceback.format_exc())
|
@@ -10,7 +10,8 @@ Run:
|
|
10
10
|
"""
|
11
11
|
import os
|
12
12
|
import sys
|
13
|
-
|
13
|
+
import asyncio
|
14
|
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
|
14
15
|
from mcp_proxy_adapter.adapter import MCPProxyAdapter
|
15
16
|
|
16
17
|
class MyRegistry:
|
@@ -57,4 +58,12 @@ if __name__ == "__main__":
|
|
57
58
|
# Print OpenAPI schema (simulated)
|
58
59
|
schema = adapter.generate_mcp_proxy_config()
|
59
60
|
print("=== Tool description from docstring ===")
|
60
|
-
print(schema.tools[0].description)
|
61
|
+
print(schema.tools[0].description)
|
62
|
+
|
63
|
+
# Call sync handler
|
64
|
+
result_sync = registry.execute('sum', a=10, b=1)
|
65
|
+
print(result_sync) # 11
|
66
|
+
|
67
|
+
# Call sync handler (ещё раз)
|
68
|
+
result_sync2 = registry.execute('sum', a=10, b=10)
|
69
|
+
print(result_sync2) # 20
|
@@ -10,7 +10,8 @@ Run:
|
|
10
10
|
"""
|
11
11
|
import os
|
12
12
|
import sys
|
13
|
-
|
13
|
+
import asyncio
|
14
|
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
|
14
15
|
from mcp_proxy_adapter.adapter import MCPProxyAdapter
|
15
16
|
|
16
17
|
class MyRegistry:
|
@@ -54,7 +55,18 @@ class MyRegistry:
|
|
54
55
|
if __name__ == "__main__":
|
55
56
|
registry = MyRegistry()
|
56
57
|
adapter = MCPProxyAdapter(registry)
|
57
|
-
|
58
|
-
|
59
|
-
print(
|
60
|
-
|
58
|
+
# Call sync handler
|
59
|
+
result_sync = registry.execute("ping")
|
60
|
+
print(result_sync) # Ping
|
61
|
+
|
62
|
+
# Call help (all)
|
63
|
+
result_help_all = registry.execute("help")
|
64
|
+
print("Help (all)", result_help_all)
|
65
|
+
|
66
|
+
# Call help (ping)
|
67
|
+
result_help_ping = registry.execute("help", command="ping")
|
68
|
+
print("Help (ping)", result_help_ping)
|
69
|
+
|
70
|
+
# Call help (notfound)
|
71
|
+
result_help_notfound = registry.execute("help", command="notfound")
|
72
|
+
print("Help (notfound)", result_help_notfound)
|
@@ -10,7 +10,8 @@ Run:
|
|
10
10
|
"""
|
11
11
|
import os
|
12
12
|
import sys
|
13
|
-
|
13
|
+
import asyncio
|
14
|
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
|
14
15
|
from mcp_proxy_adapter.adapter import MCPProxyAdapter
|
15
16
|
|
16
17
|
class MyRegistry:
|
@@ -48,6 +49,16 @@ def test_echo():
|
|
48
49
|
# Not a real FastAPI call, just for illustration
|
49
50
|
print("[TEST] Echo command passed.")
|
50
51
|
|
52
|
+
# Call sync handler
|
53
|
+
registry = MyRegistry()
|
54
|
+
adapter = MCPProxyAdapter(registry)
|
55
|
+
result_sync = registry.execute('echo', text='hi')
|
56
|
+
print(result_sync) # hi
|
57
|
+
|
58
|
+
# Call async handler
|
59
|
+
result_async = asyncio.run(registry.execute('async', x=10))
|
60
|
+
print(result_async) # 20
|
61
|
+
|
51
62
|
if __name__ == "__main__":
|
52
63
|
test_echo()
|
53
64
|
print("All tests passed.")
|
@@ -0,0 +1,47 @@
|
|
1
|
+
"""
|
2
|
+
Data models for MCP Proxy Adapter.
|
3
|
+
"""
|
4
|
+
from typing import Dict, Any, List, Optional, Union
|
5
|
+
from pydantic import BaseModel, Field
|
6
|
+
|
7
|
+
class JsonRpcRequest(BaseModel):
|
8
|
+
"""Base model for JSON-RPC requests."""
|
9
|
+
jsonrpc: str = Field(default="2.0", description="JSON-RPC version")
|
10
|
+
method: str = Field(..., description="Method name to call")
|
11
|
+
params: Dict[str, Any] = Field(default_factory=dict, description="Method parameters")
|
12
|
+
id: Optional[Union[str, int]] = Field(default=None, description="Request identifier")
|
13
|
+
|
14
|
+
class JsonRpcResponse(BaseModel):
|
15
|
+
"""Base model for JSON-RPC responses."""
|
16
|
+
jsonrpc: str = Field(default="2.0", description="JSON-RPC version")
|
17
|
+
result: Optional[Any] = Field(default=None, description="Method execution result")
|
18
|
+
error: Optional[Dict[str, Any]] = Field(default=None, description="Error information")
|
19
|
+
id: Optional[Union[str, int]] = Field(default=None, description="Request identifier")
|
20
|
+
|
21
|
+
class CommandInfo(BaseModel):
|
22
|
+
"""Command information model."""
|
23
|
+
name: str = Field(..., description="Command name")
|
24
|
+
description: str = Field(default="", description="Command description")
|
25
|
+
summary: Optional[str] = Field(default=None, description="Brief description")
|
26
|
+
parameters: Dict[str, Dict[str, Any]] = Field(default_factory=dict, description="Command parameters")
|
27
|
+
returns: Optional[Dict[str, Any]] = Field(default=None, description="Return value information")
|
28
|
+
|
29
|
+
class CommandParameter(BaseModel):
|
30
|
+
"""Command parameter model."""
|
31
|
+
type: str = Field(..., description="Parameter type")
|
32
|
+
description: str = Field(default="", description="Parameter description")
|
33
|
+
required: bool = Field(default=False, description="Whether the parameter is required")
|
34
|
+
default: Optional[Any] = Field(default=None, description="Default value")
|
35
|
+
enum: Optional[List[Any]] = Field(default=None, description="Possible values for enumeration")
|
36
|
+
|
37
|
+
class MCPProxyTool(BaseModel):
|
38
|
+
"""Tool model for MCPProxy."""
|
39
|
+
name: str = Field(..., description="Tool name")
|
40
|
+
description: str = Field(default="", description="Tool description")
|
41
|
+
parameters: Dict[str, Any] = Field(..., description="Tool parameters schema")
|
42
|
+
|
43
|
+
class MCPProxyConfig(BaseModel):
|
44
|
+
"""Configuration model for MCPProxy."""
|
45
|
+
version: str = Field(default="1.0", description="Configuration version")
|
46
|
+
tools: List[MCPProxyTool] = Field(default_factory=list, description="List of tools")
|
47
|
+
routes: List[Dict[str, Any]] = Field(default_factory=list, description="Routes configuration")
|