mcp-proxy-adapter 2.1.0__py3-none-any.whl → 2.1.2__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 (78) hide show
  1. docs/README.md +172 -0
  2. docs/README_ru.md +172 -0
  3. docs/architecture.md +251 -0
  4. docs/architecture_ru.md +343 -0
  5. docs/command_development.md +250 -0
  6. docs/command_development_ru.md +593 -0
  7. docs/deployment.md +251 -0
  8. docs/deployment_ru.md +1298 -0
  9. docs/examples.md +254 -0
  10. docs/examples_ru.md +401 -0
  11. docs/mcp_proxy_adapter.md +251 -0
  12. docs/mcp_proxy_adapter_ru.md +405 -0
  13. docs/quickstart.md +251 -0
  14. docs/quickstart_ru.md +397 -0
  15. docs/testing.md +255 -0
  16. docs/testing_ru.md +469 -0
  17. docs/validation_ru.md +287 -0
  18. examples/analyze_config.py +141 -0
  19. examples/basic_integration.py +161 -0
  20. examples/docstring_and_schema_example.py +60 -0
  21. examples/extension_example.py +60 -0
  22. examples/help_best_practices.py +67 -0
  23. examples/help_usage.py +64 -0
  24. examples/mcp_proxy_client.py +131 -0
  25. examples/mcp_proxy_config.json +175 -0
  26. examples/openapi_server.py +369 -0
  27. examples/project_structure_example.py +47 -0
  28. examples/testing_example.py +53 -0
  29. mcp_proxy_adapter/__init__.py +17 -0
  30. mcp_proxy_adapter/adapter.py +697 -0
  31. mcp_proxy_adapter/models.py +47 -0
  32. mcp_proxy_adapter/registry.py +439 -0
  33. mcp_proxy_adapter/schema.py +257 -0
  34. {mcp_proxy_adapter-2.1.0.dist-info → mcp_proxy_adapter-2.1.2.dist-info}/METADATA +2 -2
  35. mcp_proxy_adapter-2.1.2.dist-info/RECORD +61 -0
  36. mcp_proxy_adapter-2.1.2.dist-info/top_level.txt +5 -0
  37. scripts/code_analyzer/code_analyzer.py +328 -0
  38. scripts/code_analyzer/register_commands.py +446 -0
  39. scripts/publish.py +85 -0
  40. tests/conftest.py +12 -0
  41. tests/test_adapter.py +529 -0
  42. tests/test_adapter_coverage.py +274 -0
  43. tests/test_basic_dispatcher.py +169 -0
  44. tests/test_command_registry.py +328 -0
  45. tests/test_examples.py +32 -0
  46. tests/test_mcp_proxy_adapter.py +568 -0
  47. tests/test_mcp_proxy_adapter_basic.py +262 -0
  48. tests/test_part1.py +348 -0
  49. tests/test_part2.py +524 -0
  50. tests/test_schema.py +358 -0
  51. tests/test_simple_adapter.py +251 -0
  52. adapters/__init__.py +0 -16
  53. cli/__init__.py +0 -12
  54. cli/__main__.py +0 -79
  55. cli/command_runner.py +0 -233
  56. generators/__init__.py +0 -14
  57. generators/endpoint_generator.py +0 -172
  58. generators/openapi_generator.py +0 -254
  59. generators/rest_api_generator.py +0 -207
  60. mcp_proxy_adapter-2.1.0.dist-info/RECORD +0 -28
  61. mcp_proxy_adapter-2.1.0.dist-info/top_level.txt +0 -7
  62. openapi_schema/__init__.py +0 -38
  63. openapi_schema/command_registry.py +0 -312
  64. openapi_schema/rest_schema.py +0 -510
  65. openapi_schema/rpc_generator.py +0 -307
  66. openapi_schema/rpc_schema.py +0 -416
  67. validators/__init__.py +0 -14
  68. validators/base_validator.py +0 -23
  69. {analyzers → mcp_proxy_adapter/analyzers}/__init__.py +0 -0
  70. {analyzers → mcp_proxy_adapter/analyzers}/docstring_analyzer.py +0 -0
  71. {analyzers → mcp_proxy_adapter/analyzers}/type_analyzer.py +0 -0
  72. {dispatchers → mcp_proxy_adapter/dispatchers}/__init__.py +0 -0
  73. {dispatchers → mcp_proxy_adapter/dispatchers}/base_dispatcher.py +0 -0
  74. {dispatchers → mcp_proxy_adapter/dispatchers}/json_rpc_dispatcher.py +0 -0
  75. {validators → mcp_proxy_adapter/validators}/docstring_validator.py +0 -0
  76. {validators → mcp_proxy_adapter/validators}/metadata_validator.py +0 -0
  77. {mcp_proxy_adapter-2.1.0.dist-info → mcp_proxy_adapter-2.1.2.dist-info}/WHEEL +0 -0
  78. {mcp_proxy_adapter-2.1.0.dist-info → mcp_proxy_adapter-2.1.2.dist-info}/licenses/LICENSE +0 -0
cli/__main__.py DELETED
@@ -1,79 +0,0 @@
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()
cli/command_runner.py DELETED
@@ -1,233 +0,0 @@
1
- """
2
- Command line utility for executing commands.
3
-
4
- Provides a command line interface for the command dispatcher,
5
- allowing execution of registered commands and getting help information.
6
- """
7
-
8
- import argparse
9
- import json
10
- import sys
11
- from typing import Any, Dict, List, Optional, Sequence
12
-
13
- from command_registry.dispatchers.base_dispatcher import BaseDispatcher
14
-
15
-
16
- class CommandRunner:
17
- """
18
- Command line utility for executing commands.
19
-
20
- Converts command line arguments into dispatcher command calls.
21
- Provides ability to get help information about commands.
22
- """
23
-
24
- def __init__(self, dispatcher: BaseDispatcher):
25
- """
26
- Initializes CommandRunner.
27
-
28
- Args:
29
- dispatcher: Command dispatcher for executing commands
30
- """
31
- self.dispatcher = dispatcher
32
-
33
- def build_parser(self) -> argparse.ArgumentParser:
34
- """
35
- Creates argument parser based on registered commands.
36
-
37
- Returns:
38
- Command line argument parser
39
- """
40
- parser = argparse.ArgumentParser(
41
- description="Execute commands from command registry",
42
- add_help=False, # Disable standard help
43
- )
44
-
45
- # Add main arguments
46
- parser.add_argument(
47
- "command",
48
- help="Command name to execute or 'help' to get list of commands",
49
- nargs="?",
50
- default="help",
51
- )
52
-
53
- parser.add_argument(
54
- "--help", "-h",
55
- action="store_true",
56
- help="Show help for specified command",
57
- dest="show_help",
58
- )
59
-
60
- parser.add_argument(
61
- "--json",
62
- action="store_true",
63
- help="Output result in JSON format",
64
- )
65
-
66
- return parser
67
-
68
- def _handle_help_command(self, args: Optional[List[str]] = None) -> None:
69
- """
70
- Outputs help information about available commands.
71
-
72
- Args:
73
- args: List of arguments, if first argument is command name,
74
- outputs help for that command
75
- """
76
- if args and len(args) > 0 and args[0] != "help":
77
- # Help for specific command
78
- command_name = args[0]
79
- if command_name not in self.dispatcher.get_valid_commands():
80
- print(f"Unknown command: {command_name}", file=sys.stderr)
81
- return
82
-
83
- info = self.dispatcher.get_command_info(command_name)
84
-
85
- print(f"\nCommand: {command_name}\n")
86
-
87
- if info.get("description"):
88
- print(f"Description: {info['description']}\n")
89
-
90
- if "parameters" in info and info["parameters"]:
91
- print("Parameters:")
92
- for name, param_info in info["parameters"].items():
93
- param_type = param_info.get("type", "any")
94
- required = param_info.get("required", False)
95
- description = param_info.get("description", "")
96
-
97
- req_str = " (required)" if required else ""
98
- print(f" {name} ({param_type}){req_str}")
99
- if description:
100
- print(f" {description}")
101
- print()
102
-
103
- if "returns" in info and info["returns"]:
104
- print(f"Returns: {info['returns']}")
105
-
106
- print("\nUsage:")
107
- params_str = " ".join(
108
- f"--{name}=<value>" for name in info.get("parameters", {})
109
- )
110
- print(f" python -m command_registry.cli {command_name} {params_str}")
111
- else:
112
- # General help
113
- commands = self.dispatcher.get_commands_info()
114
-
115
- print("\nAvailable commands:\n")
116
- for name, info in commands.items():
117
- description = info.get("description", "No description")
118
- # Show only first line of description for brevity
119
- short_desc = description.split("\n")[0]
120
- print(f" {name:<20} {short_desc}")
121
-
122
- print("\nTo get detailed information about a command use:")
123
- print(" python -m command_registry.cli help <command>")
124
- print(" python -m command_registry.cli <command> --help")
125
-
126
- def run(self, args: Sequence[str]) -> None:
127
- """
128
- Runs command based on provided arguments.
129
-
130
- Args:
131
- args: Command line arguments (without program name)
132
- """
133
- parser = self.build_parser()
134
-
135
- # Parse arguments
136
- parsed_args, remaining = parser.parse_known_args(args)
137
-
138
- # Handle help command or --help flag
139
- if parsed_args.command == "help" or parsed_args.show_help:
140
- if parsed_args.command == "help":
141
- self._handle_help_command(remaining)
142
- else:
143
- self._handle_help_command([parsed_args.command])
144
- return
145
-
146
- # Check command existence
147
- if parsed_args.command not in self.dispatcher.get_valid_commands():
148
- print(f"Unknown command: {parsed_args.command}", file=sys.stderr)
149
- print("Use 'help' to get list of available commands", file=sys.stderr)
150
- sys.exit(1)
151
-
152
- # Convert remaining arguments to command parameters
153
- command_params = {}
154
- command_info = self.dispatcher.get_command_info(parsed_args.command)
155
- expected_params = command_info.get("parameters", {})
156
-
157
- # Parse parameters from remaining arguments
158
- i = 0
159
- while i < len(remaining):
160
- arg = remaining[i]
161
-
162
- # Support --param=value format
163
- if arg.startswith("--") and "=" in arg:
164
- param_name, value = arg[2:].split("=", 1)
165
- command_params[param_name] = self._parse_value(value)
166
- i += 1
167
- # Support --param value format
168
- elif arg.startswith("--"):
169
- param_name = arg[2:]
170
- if i + 1 < len(remaining) and not remaining[i + 1].startswith("--"):
171
- command_params[param_name] = self._parse_value(remaining[i + 1])
172
- i += 2
173
- else:
174
- # Boolean flag
175
- command_params[param_name] = True
176
- i += 1
177
- else:
178
- print(f"Unknown argument: {arg}", file=sys.stderr)
179
- i += 1
180
-
181
- # Check required parameters
182
- for param_name, param_info in expected_params.items():
183
- if param_info.get("required", False) and param_name not in command_params:
184
- print(f"Missing required parameter: {param_name}", file=sys.stderr)
185
- sys.exit(1)
186
-
187
- try:
188
- # Execute command
189
- result = self.dispatcher.execute(parsed_args.command, **command_params)
190
-
191
- # Output result
192
- if parsed_args.json:
193
- print(json.dumps(result, ensure_ascii=False, indent=2))
194
- elif result is not None:
195
- print(result)
196
- except Exception as e:
197
- print(f"Error executing command: {e}", file=sys.stderr)
198
- sys.exit(1)
199
-
200
- def _parse_value(self, value_str: str) -> Any:
201
- """
202
- Parses string value into corresponding type.
203
-
204
- Args:
205
- value_str: String representation of value
206
-
207
- Returns:
208
- Parsed value of corresponding type
209
- """
210
- # Boolean values
211
- if value_str.lower() in ("true", "yes", "y", "1"):
212
- return True
213
- if value_str.lower() in ("false", "no", "n", "0"):
214
- return False
215
-
216
- # Numbers
217
- try:
218
- if "." in value_str:
219
- return float(value_str)
220
- return int(value_str)
221
- except ValueError:
222
- pass
223
-
224
- # JSON
225
- if (value_str.startswith("{") and value_str.endswith("}")) or \
226
- (value_str.startswith("[") and value_str.endswith("]")):
227
- try:
228
- return json.loads(value_str)
229
- except json.JSONDecodeError:
230
- pass
231
-
232
- # Default - string
233
- return value_str
generators/__init__.py DELETED
@@ -1,14 +0,0 @@
1
- """
2
- Генераторы API на основе команд и их метаданных.
3
-
4
- Этот модуль содержит классы для автоматической генерации API интерфейсов
5
- (REST, OpenAPI и др.) на основе зарегистрированных команд.
6
- """
7
-
8
- from command_registry.generators.rest_api_generator import RestApiGenerator
9
- from command_registry.generators.openapi_generator import OpenApiGenerator
10
-
11
- __all__ = [
12
- 'RestApiGenerator',
13
- 'OpenApiGenerator',
14
- ]
@@ -1,172 +0,0 @@
1
- """
2
- REST API endpoint generator based on registered commands.
3
- """
4
- from typing import Any, Callable, Dict, List, Optional
5
- import inspect
6
- import asyncio
7
- from fastapi import APIRouter, Depends, Request, HTTPException
8
- from pydantic import BaseModel, create_model
9
-
10
- class EndpointGenerator:
11
- """
12
- REST API endpoint generator based on registered commands.
13
-
14
- Creates dynamic FastAPI endpoints by automatically generating
15
- request and response models based on signatures and docstrings
16
- of registered handler functions.
17
- """
18
-
19
- def __init__(self, router: APIRouter, dispatcher: Any):
20
- """
21
- Initialize endpoint generator.
22
-
23
- Args:
24
- router: FastAPI router for registering endpoints
25
- dispatcher: Command dispatcher providing access to registered commands
26
- """
27
- self.router = router
28
- self.dispatcher = dispatcher
29
- self.registered_endpoints = []
30
-
31
- def generate_endpoint(self, command_name: str, handler_func: Callable, metadata: Dict[str, Any]) -> None:
32
- """
33
- Generates REST API endpoint for specified command.
34
-
35
- Args:
36
- command_name: Command name
37
- handler_func: Command handler function
38
- metadata: Command metadata from docstring
39
- """
40
- # Get function signature
41
- sig = inspect.signature(handler_func)
42
-
43
- # Create request model based on function parameters
44
- param_fields = {}
45
- for name, param in sig.parameters.items():
46
- # Skip self parameter
47
- if name == 'self':
48
- continue
49
-
50
- # Get parameter type and default value
51
- param_type = param.annotation if param.annotation != inspect.Parameter.empty else Any
52
- default_value = ... if param.default == inspect.Parameter.empty else param.default
53
-
54
- # Add field to model
55
- param_fields[name] = (param_type, default_value)
56
-
57
- # Create request model
58
- request_model = create_model(
59
- f"{command_name.capitalize()}Request",
60
- **param_fields
61
- )
62
-
63
- # Create endpoint
64
- endpoint_path = f"/{command_name}"
65
-
66
- # Define endpoint handler
67
- async def endpoint_handler(request_data: request_model):
68
- # Call command through dispatcher
69
- try:
70
- # Get parameters from model
71
- params = request_data.__dict__ if hasattr(request_data, "__dict__") else {}
72
-
73
- # Call dispatcher's execute method
74
- result = self.dispatcher.execute(command_name, **params)
75
-
76
- # If result is coroutine, await its completion
77
- if inspect.iscoroutine(result):
78
- result = await result
79
-
80
- return {"success": True, "result": result}
81
- except Exception as e:
82
- raise HTTPException(status_code=500, detail=str(e))
83
-
84
- # Add documentation from metadata
85
- if 'description' in metadata:
86
- endpoint_handler.__doc__ = metadata['description']
87
-
88
- # Register endpoint
89
- self.router.post(endpoint_path, response_model=None)(endpoint_handler)
90
- self.registered_endpoints.append(endpoint_path)
91
-
92
- def generate_all_endpoints(self) -> List[str]:
93
- """
94
- Generates endpoints for all registered commands.
95
-
96
- Returns:
97
- List[str]: List of created endpoints
98
- """
99
- # Create endpoints for all commands
100
- commands_info = self.dispatcher.get_commands_info()
101
-
102
- for command_name, command_info in commands_info.items():
103
- # Get command handler
104
- handler = self.dispatcher._handlers[command_name] if hasattr(self.dispatcher, "_handlers") else None
105
-
106
- # If handler couldn't be obtained, skip command
107
- if not handler:
108
- continue
109
-
110
- self.generate_endpoint(
111
- command_name,
112
- handler,
113
- command_info
114
- )
115
-
116
- # Create help endpoint
117
- self.generate_help_endpoint()
118
-
119
- return self.registered_endpoints
120
-
121
- def generate_help_endpoint(self) -> None:
122
- """
123
- Creates special /help endpoint that returns information
124
- about all available commands and their endpoints.
125
- """
126
- async def help_handler(command: Optional[str] = None):
127
- if command:
128
- # If specific command is specified, return information about it
129
- command_info = self.dispatcher.get_command_info(command)
130
- if not command_info:
131
- return {
132
- "success": False,
133
- "error": f"Command '{command}' not found",
134
- "available_commands": self.dispatcher.get_valid_commands()
135
- }
136
-
137
- # Add endpoint URL
138
- endpoint_path = f"/{command}"
139
-
140
- return {
141
- "success": True,
142
- "command": command,
143
- "info": command_info,
144
- "endpoint": endpoint_path
145
- }
146
-
147
- # Otherwise return information about all commands
148
- commands_info = {}
149
- for cmd in self.dispatcher.get_valid_commands():
150
- info = self.dispatcher.get_command_info(cmd)
151
- if not info:
152
- continue
153
-
154
- endpoint_path = f"/{cmd}"
155
- commands_info[cmd] = {
156
- "summary": info.get("summary", ""),
157
- "description": info.get("description", ""),
158
- "endpoint": endpoint_path,
159
- "params_count": len(info.get("params", {}))
160
- }
161
-
162
- return {
163
- "success": True,
164
- "commands": commands_info,
165
- "total": len(commands_info),
166
- "endpoints": self.registered_endpoints,
167
- "note": "Use 'command' parameter to get detailed information about a specific command"
168
- }
169
-
170
- help_handler.__doc__ = "Get list of all available commands and API endpoints"
171
- self.router.get("/help", response_model=None)(help_handler)
172
- self.registered_endpoints.append("/help")