mcp-proxy-adapter 2.1.13__py3-none-any.whl → 2.1.15__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 +14 -5
- mcp_proxy_adapter/examples/extension_example.py +17 -5
- mcp_proxy_adapter/examples/project_structure_example.py +7 -2
- mcp_proxy_adapter/examples/testing_example.py +25 -3
- mcp_proxy_adapter/models.py +47 -0
- mcp_proxy_adapter/registry.py +437 -0
- mcp_proxy_adapter/schema.py +257 -0
- mcp_proxy_adapter/testing_utils.py +11 -3
- 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.15.dist-info}/METADATA +1 -1
- mcp_proxy_adapter-2.1.15.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.15.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-2.1.13.dist-info → mcp_proxy_adapter-2.1.15.dist-info}/licenses/LICENSE +0 -0
- {mcp_proxy_adapter-2.1.13.dist-info → mcp_proxy_adapter-2.1.15.dist-info}/top_level.txt +0 -0
@@ -4,6 +4,9 @@ Test utilities for MCP Proxy Adapter: mock dispatcher, registry, and OpenAPI gen
|
|
4
4
|
Can be used in examples and tests.
|
5
5
|
"""
|
6
6
|
|
7
|
+
import asyncio
|
8
|
+
import inspect
|
9
|
+
|
7
10
|
def success_command(value: int = 1) -> dict:
|
8
11
|
return {"result": value * 2}
|
9
12
|
|
@@ -65,13 +68,18 @@ class MockDispatcher:
|
|
65
68
|
def execute_from_params(self, **params):
|
66
69
|
if "query" in params and params["query"] in self.commands:
|
67
70
|
command = params.pop("query")
|
68
|
-
|
71
|
+
result = self.execute(command, **params)
|
72
|
+
return result
|
69
73
|
return {"available_commands": self.get_valid_commands(), "received_params": params}
|
70
74
|
|
71
|
-
def execute(self, command, **params):
|
75
|
+
async def execute(self, command, **params):
|
72
76
|
if command not in self.commands:
|
73
77
|
raise KeyError(f"Unknown command: {command}")
|
74
|
-
|
78
|
+
handler = self.commands[command]
|
79
|
+
if inspect.iscoroutinefunction(handler):
|
80
|
+
return await handler(**params)
|
81
|
+
loop = asyncio.get_running_loop()
|
82
|
+
return await loop.run_in_executor(None, lambda: handler(**params))
|
75
83
|
|
76
84
|
def get_valid_commands(self):
|
77
85
|
return list(self.commands.keys())
|
@@ -0,0 +1,75 @@
|
|
1
|
+
"""
|
2
|
+
Validator for checking the correspondence between docstrings and function signatures.
|
3
|
+
"""
|
4
|
+
import inspect
|
5
|
+
from typing import Dict, Any, Optional, Callable, List, Tuple, get_type_hints
|
6
|
+
import docstring_parser
|
7
|
+
|
8
|
+
class DocstringValidator:
|
9
|
+
"""
|
10
|
+
Validator for checking the correspondence between docstrings and handler functions.
|
11
|
+
|
12
|
+
This class verifies that function docstrings match their signatures,
|
13
|
+
contain all necessary sections, and describe all parameters.
|
14
|
+
"""
|
15
|
+
|
16
|
+
def validate(self, handler: Callable, command_name: str, metadata: Dict[str, Any]) -> Tuple[bool, List[str]]:
|
17
|
+
"""
|
18
|
+
Validates the function's docstring against its formal parameters.
|
19
|
+
|
20
|
+
Args:
|
21
|
+
handler: Command handler function
|
22
|
+
command_name: Command name
|
23
|
+
metadata: Command metadata
|
24
|
+
|
25
|
+
Returns:
|
26
|
+
Tuple[bool, List[str]]: Validity flag and list of errors
|
27
|
+
"""
|
28
|
+
errors = []
|
29
|
+
|
30
|
+
# Get formal parameters of the function
|
31
|
+
sig = inspect.signature(handler)
|
32
|
+
formal_params = list(sig.parameters.keys())
|
33
|
+
|
34
|
+
# Skip self parameter for methods
|
35
|
+
if formal_params and formal_params[0] == 'self':
|
36
|
+
formal_params = formal_params[1:]
|
37
|
+
|
38
|
+
# Parse docstring
|
39
|
+
docstring = handler.__doc__ or ""
|
40
|
+
parsed_doc = docstring_parser.parse(docstring)
|
41
|
+
|
42
|
+
# Check for function description
|
43
|
+
if not parsed_doc.short_description and not parsed_doc.long_description:
|
44
|
+
errors.append(f"Missing function description")
|
45
|
+
|
46
|
+
# Get parameters from docstring
|
47
|
+
doc_params = {param.arg_name: param for param in parsed_doc.params}
|
48
|
+
|
49
|
+
# Check that all formal parameters are described in the docstring
|
50
|
+
for param in formal_params:
|
51
|
+
# Skip special parameters
|
52
|
+
if param in ('params', 'kwargs'):
|
53
|
+
continue
|
54
|
+
|
55
|
+
if param not in doc_params:
|
56
|
+
errors.append(f"Parameter '{param}' is not described in the function docstring")
|
57
|
+
|
58
|
+
# Check for returns in docstring
|
59
|
+
if not parsed_doc.returns and not any(t.type_name == 'Returns' for t in parsed_doc.meta):
|
60
|
+
errors.append(f"Missing return value description in the function docstring")
|
61
|
+
|
62
|
+
# Check for type annotations
|
63
|
+
try:
|
64
|
+
type_hints = get_type_hints(handler)
|
65
|
+
for param in formal_params:
|
66
|
+
# Skip special parameters
|
67
|
+
if param in ('params', 'kwargs'):
|
68
|
+
continue
|
69
|
+
|
70
|
+
if param not in type_hints:
|
71
|
+
errors.append(f"Missing type annotation for parameter '{param}' in function {command_name}")
|
72
|
+
except Exception as e:
|
73
|
+
errors.append(f"Error getting type hints: {str(e)}")
|
74
|
+
|
75
|
+
return len(errors) == 0, errors
|
@@ -0,0 +1,76 @@
|
|
1
|
+
"""
|
2
|
+
Validator for checking command metadata against function signatures.
|
3
|
+
"""
|
4
|
+
import inspect
|
5
|
+
from typing import Dict, Any, Optional, Callable, List, Tuple
|
6
|
+
|
7
|
+
class MetadataValidator:
|
8
|
+
"""
|
9
|
+
Validator for checking handler function metadata.
|
10
|
+
|
11
|
+
This class verifies that command metadata matches function signatures,
|
12
|
+
and all parameters are correctly described.
|
13
|
+
"""
|
14
|
+
|
15
|
+
def validate(self, handler: Callable, command_name: str, metadata: Dict[str, Any]) -> Tuple[bool, List[str]]:
|
16
|
+
"""
|
17
|
+
Checks if metadata matches function's formal parameters.
|
18
|
+
|
19
|
+
Args:
|
20
|
+
handler: Command handler function
|
21
|
+
command_name: Command name
|
22
|
+
metadata: Command metadata
|
23
|
+
|
24
|
+
Returns:
|
25
|
+
Tuple[bool, List[str]]: Validity flag and list of errors
|
26
|
+
"""
|
27
|
+
errors = []
|
28
|
+
|
29
|
+
# Check presence of main fields in metadata
|
30
|
+
if not metadata.get('description'):
|
31
|
+
errors.append(f"Missing description for command '{command_name}'")
|
32
|
+
|
33
|
+
# Get function's formal parameters
|
34
|
+
sig = inspect.signature(handler)
|
35
|
+
formal_params = list(sig.parameters.keys())
|
36
|
+
|
37
|
+
# Skip self parameter for methods
|
38
|
+
if formal_params and formal_params[0] == 'self':
|
39
|
+
formal_params = formal_params[1:]
|
40
|
+
|
41
|
+
# Check presence of parameters in metadata
|
42
|
+
if 'parameters' not in metadata or not isinstance(metadata['parameters'], dict):
|
43
|
+
errors.append(f"Parameters are missing or incorrectly defined in metadata for command '{command_name}'")
|
44
|
+
return False, errors
|
45
|
+
|
46
|
+
meta_params = metadata['parameters']
|
47
|
+
|
48
|
+
# Check that all formal parameters are in metadata
|
49
|
+
for param in formal_params:
|
50
|
+
# Skip special parameters
|
51
|
+
if param in ('params', 'kwargs'):
|
52
|
+
continue
|
53
|
+
|
54
|
+
if param not in meta_params:
|
55
|
+
errors.append(f"Parameter '{param}' is not described in metadata for command '{command_name}'")
|
56
|
+
continue
|
57
|
+
|
58
|
+
param_info = meta_params[param]
|
59
|
+
|
60
|
+
# Check presence of required fields for parameter
|
61
|
+
if not isinstance(param_info, dict):
|
62
|
+
errors.append(f"Incorrect format for parameter '{param}' description in metadata for command '{command_name}'")
|
63
|
+
continue
|
64
|
+
|
65
|
+
if 'type' not in param_info:
|
66
|
+
errors.append(f"Type is not specified for parameter '{param}' in metadata for command '{command_name}'")
|
67
|
+
|
68
|
+
if 'description' not in param_info:
|
69
|
+
errors.append(f"Description is not specified for parameter '{param}' in metadata for command '{command_name}'")
|
70
|
+
|
71
|
+
# Check that there are no extra parameters in metadata
|
72
|
+
for param in meta_params:
|
73
|
+
if param not in formal_params and param not in ('params', 'kwargs'):
|
74
|
+
errors.append(f"Extra parameter '{param}' in metadata for command '{command_name}'")
|
75
|
+
|
76
|
+
return len(errors) == 0, errors
|
@@ -0,0 +1,28 @@
|
|
1
|
+
mcp_proxy_adapter/adapter.py,sha256=x5pT-t4uT12O3GzLurrKBSQ_hwVpjhCRx5oZ5AdZnpY,28856
|
2
|
+
mcp_proxy_adapter/models.py,sha256=8zVWU6ly18pWozOnKQ2gsGpmTgL37-fFE_Fr1SDW-Nk,2530
|
3
|
+
mcp_proxy_adapter/registry.py,sha256=IXzMthYRHvEq37Y99ID49kv1ovqb-RFccKznBKxBRuc,15926
|
4
|
+
mcp_proxy_adapter/schema.py,sha256=HZM0TTQTSi8ha1TEeVevdCyGZOUPoT1soB7Nex0hV50,10947
|
5
|
+
mcp_proxy_adapter/testing_utils.py,sha256=5drf9PFUcmUiShNZXN5x6FSbDzB-2jCAb1RvleUanW0,4510
|
6
|
+
mcp_proxy_adapter/analyzers/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
|
7
|
+
mcp_proxy_adapter/analyzers/docstring_analyzer.py,sha256=T3FLJEo_uChShfiEKRl8GpVoHvh5HiudZkxnj4KixfA,7541
|
8
|
+
mcp_proxy_adapter/analyzers/type_analyzer.py,sha256=6Wac7osKwF03waFSwQ8ZM0Wqn_zAP2D-I4WMEpR0hQM,5230
|
9
|
+
mcp_proxy_adapter/dispatchers/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
|
10
|
+
mcp_proxy_adapter/dispatchers/base_dispatcher.py,sha256=G9_dMwboNmpvg9OapWKEXI52QsEiIigfjLMs_7tMbNg,2356
|
11
|
+
mcp_proxy_adapter/dispatchers/json_rpc_dispatcher.py,sha256=sdRwvI5f-2dtI7U_sv6-pqUvxBMirgUDl_P7la3EV0A,8054
|
12
|
+
mcp_proxy_adapter/examples/analyze_config.py,sha256=vog7TNHDw5ZoYhQLbAvZvEoufmQwH54KJzQBJrSq5w4,4283
|
13
|
+
mcp_proxy_adapter/examples/basic_integration.py,sha256=mtRval4VSUgTb_C2p8U_DPPSEKA08dZYKZk-bOrE4H4,4470
|
14
|
+
mcp_proxy_adapter/examples/docstring_and_schema_example.py,sha256=8a6k7_wG1afnjzqCD6W_LBM-N8k3t0w1H_au3-14ds8,2201
|
15
|
+
mcp_proxy_adapter/examples/extension_example.py,sha256=2UnrcHw0yRZuFzyvW6zsJ8_NTmGWU79fnCEG6w8VRDY,2525
|
16
|
+
mcp_proxy_adapter/examples/help_best_practices.py,sha256=Bit9Ywl9vGvM_kuV8DJ6pIDK4mY4mF2Gia9rLc56RpI,2646
|
17
|
+
mcp_proxy_adapter/examples/help_usage.py,sha256=JIUsZofdLFyI7FcwPF-rLxipF1-HaZINzVK1KBh0vxA,2577
|
18
|
+
mcp_proxy_adapter/examples/mcp_proxy_client.py,sha256=z4IzFlGigVTQSb8TpcrQ_a0migsmC58LnNwc8wZmTfw,3811
|
19
|
+
mcp_proxy_adapter/examples/openapi_server.py,sha256=5gRM-EHvMsnNtS_M6l_pNPN5EkSf4X1Lcq4E1Xs5tp0,13387
|
20
|
+
mcp_proxy_adapter/examples/project_structure_example.py,sha256=aWQwQqNn3JOCAB6ngYx_JOUh8uy73B6Q51r6HsPUUmM,1593
|
21
|
+
mcp_proxy_adapter/examples/testing_example.py,sha256=s_ln2U7sMdCey87gmrh9-SjZMF2EW602uGhI1rDsCaM,2461
|
22
|
+
mcp_proxy_adapter/validators/docstring_validator.py,sha256=Onpq2iNJ1qF4ejkJJIlBkLROuSNIVALHVmXIgkCpaFI,2934
|
23
|
+
mcp_proxy_adapter/validators/metadata_validator.py,sha256=uCrn38-VYYn89l6f5CC_GoTAHAweaOW2Z6Esro1rtGw,3155
|
24
|
+
mcp_proxy_adapter-2.1.15.dist-info/licenses/LICENSE,sha256=OkApFEwdgMCt_mbvUI-eIwKMSTe38K3XnU2DT5ub-wI,1072
|
25
|
+
mcp_proxy_adapter-2.1.15.dist-info/METADATA,sha256=nUnnfN0k4rxhUdLgJKk6hJd_6S9I5hswuG3baEHL42Q,8886
|
26
|
+
mcp_proxy_adapter-2.1.15.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
|
27
|
+
mcp_proxy_adapter-2.1.15.dist-info/top_level.txt,sha256=JZT7vPLBYrtroX-ij68JBhJYbjDdghcV-DFySRy-Nnw,18
|
28
|
+
mcp_proxy_adapter-2.1.15.dist-info/RECORD,,
|
@@ -1,19 +0,0 @@
|
|
1
|
-
mcp_proxy_adapter/testing_utils.py,sha256=RWjQFNSUtVkeP0qNzp6_jrT6_tub3w_052DrRmvxVk0,4243
|
2
|
-
mcp_proxy_adapter/analyzers/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
|
3
|
-
mcp_proxy_adapter/dispatchers/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
|
4
|
-
mcp_proxy_adapter/dispatchers/json_rpc_dispatcher.py,sha256=Ibp-4d4kKf_AW_yv_27Zb_PgFIfa3tZLmuppMg8YqsY,8034
|
5
|
-
mcp_proxy_adapter/examples/analyze_config.py,sha256=vog7TNHDw5ZoYhQLbAvZvEoufmQwH54KJzQBJrSq5w4,4283
|
6
|
-
mcp_proxy_adapter/examples/basic_integration.py,sha256=mtRval4VSUgTb_C2p8U_DPPSEKA08dZYKZk-bOrE4H4,4470
|
7
|
-
mcp_proxy_adapter/examples/docstring_and_schema_example.py,sha256=c96L4KF_7yWzffmvd4hyeQuXSdYyYkv7Uvuy0QxgMcQ,1929
|
8
|
-
mcp_proxy_adapter/examples/extension_example.py,sha256=vnatnFdNTapMpPcQ79Ugitk92ZiUfpLTs7Dvsodf1og,2277
|
9
|
-
mcp_proxy_adapter/examples/help_best_practices.py,sha256=Bit9Ywl9vGvM_kuV8DJ6pIDK4mY4mF2Gia9rLc56RpI,2646
|
10
|
-
mcp_proxy_adapter/examples/help_usage.py,sha256=JIUsZofdLFyI7FcwPF-rLxipF1-HaZINzVK1KBh0vxA,2577
|
11
|
-
mcp_proxy_adapter/examples/mcp_proxy_client.py,sha256=z4IzFlGigVTQSb8TpcrQ_a0migsmC58LnNwc8wZmTfw,3811
|
12
|
-
mcp_proxy_adapter/examples/openapi_server.py,sha256=5gRM-EHvMsnNtS_M6l_pNPN5EkSf4X1Lcq4E1Xs5tp0,13387
|
13
|
-
mcp_proxy_adapter/examples/project_structure_example.py,sha256=sswTo6FZb1F5juHa0FYG3cgvrh3wfgGfJu2bBy5tCm4,1460
|
14
|
-
mcp_proxy_adapter/examples/testing_example.py,sha256=AB13c4C1bjs1145O-yriwyreeVXtMOlQLzs2BCGmprk,1719
|
15
|
-
mcp_proxy_adapter-2.1.13.dist-info/licenses/LICENSE,sha256=OkApFEwdgMCt_mbvUI-eIwKMSTe38K3XnU2DT5ub-wI,1072
|
16
|
-
mcp_proxy_adapter-2.1.13.dist-info/METADATA,sha256=1flBa3KLq1QMMf9T-NcpgpP3LxJY4_BwMAUW21K915Q,8886
|
17
|
-
mcp_proxy_adapter-2.1.13.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
|
18
|
-
mcp_proxy_adapter-2.1.13.dist-info/top_level.txt,sha256=JZT7vPLBYrtroX-ij68JBhJYbjDdghcV-DFySRy-Nnw,18
|
19
|
-
mcp_proxy_adapter-2.1.13.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|