mcp-proxy-adapter 2.1.17__py3-none-any.whl → 3.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.
Files changed (84) hide show
  1. examples/__init__.py +19 -0
  2. examples/anti_patterns/README.md +51 -0
  3. examples/anti_patterns/__init__.py +9 -0
  4. examples/anti_patterns/bad_design/README.md +72 -0
  5. examples/anti_patterns/bad_design/global_state.py +170 -0
  6. examples/anti_patterns/bad_design/monolithic_command.py +272 -0
  7. examples/basic_example/README.md +131 -0
  8. examples/basic_example/__init__.py +8 -0
  9. examples/basic_example/commands/__init__.py +5 -0
  10. examples/basic_example/commands/echo_command.py +95 -0
  11. examples/basic_example/commands/math_command.py +151 -0
  12. examples/basic_example/commands/time_command.py +152 -0
  13. examples/basic_example/config.json +21 -0
  14. examples/basic_example/config.yaml +20 -0
  15. examples/basic_example/docs/EN/README.md +136 -0
  16. examples/basic_example/docs/RU/README.md +136 -0
  17. examples/basic_example/main.py +50 -0
  18. examples/basic_example/server.py +45 -0
  19. examples/basic_example/tests/conftest.py +243 -0
  20. examples/commands/echo_command.py +52 -0
  21. examples/commands/echo_result.py +65 -0
  22. examples/commands/get_date_command.py +98 -0
  23. examples/commands/new_uuid4_command.py +91 -0
  24. examples/complete_example/Dockerfile +24 -0
  25. examples/complete_example/README.md +92 -0
  26. examples/complete_example/__init__.py +8 -0
  27. examples/complete_example/commands/__init__.py +5 -0
  28. examples/complete_example/commands/system_command.py +327 -0
  29. examples/complete_example/config.json +41 -0
  30. examples/complete_example/configs/config.dev.yaml +40 -0
  31. examples/complete_example/configs/config.docker.yaml +40 -0
  32. examples/complete_example/docker-compose.yml +35 -0
  33. examples/complete_example/main.py +67 -0
  34. examples/complete_example/requirements.txt +20 -0
  35. examples/complete_example/server.py +85 -0
  36. examples/minimal_example/README.md +51 -0
  37. examples/minimal_example/__init__.py +8 -0
  38. examples/minimal_example/config.json +21 -0
  39. examples/minimal_example/config.yaml +26 -0
  40. examples/minimal_example/main.py +67 -0
  41. examples/minimal_example/simple_server.py +124 -0
  42. examples/minimal_example/tests/conftest.py +171 -0
  43. examples/minimal_example/tests/test_hello_command.py +111 -0
  44. examples/minimal_example/tests/test_integration.py +183 -0
  45. examples/server.py +69 -0
  46. examples/simple_server.py +137 -0
  47. examples/test_server.py +126 -0
  48. mcp_proxy_adapter/__init__.py +33 -1
  49. mcp_proxy_adapter/config.py +186 -0
  50. mcp_proxy_adapter/custom_openapi.py +125 -0
  51. mcp_proxy_adapter/framework.py +109 -0
  52. mcp_proxy_adapter/openapi.py +403 -0
  53. mcp_proxy_adapter/version.py +3 -0
  54. mcp_proxy_adapter-3.0.0.dist-info/METADATA +200 -0
  55. mcp_proxy_adapter-3.0.0.dist-info/RECORD +58 -0
  56. {mcp_proxy_adapter-2.1.17.dist-info → mcp_proxy_adapter-3.0.0.dist-info}/top_level.txt +1 -0
  57. mcp_proxy_adapter/adapter.py +0 -697
  58. mcp_proxy_adapter/analyzers/__init__.py +0 -1
  59. mcp_proxy_adapter/analyzers/docstring_analyzer.py +0 -199
  60. mcp_proxy_adapter/analyzers/type_analyzer.py +0 -151
  61. mcp_proxy_adapter/dispatchers/__init__.py +0 -1
  62. mcp_proxy_adapter/dispatchers/base_dispatcher.py +0 -85
  63. mcp_proxy_adapter/dispatchers/json_rpc_dispatcher.py +0 -262
  64. mcp_proxy_adapter/examples/analyze_config.py +0 -141
  65. mcp_proxy_adapter/examples/basic_integration.py +0 -155
  66. mcp_proxy_adapter/examples/docstring_and_schema_example.py +0 -69
  67. mcp_proxy_adapter/examples/extension_example.py +0 -72
  68. mcp_proxy_adapter/examples/help_best_practices.py +0 -67
  69. mcp_proxy_adapter/examples/help_usage.py +0 -64
  70. mcp_proxy_adapter/examples/mcp_proxy_client.py +0 -131
  71. mcp_proxy_adapter/examples/openapi_server.py +0 -383
  72. mcp_proxy_adapter/examples/project_structure_example.py +0 -47
  73. mcp_proxy_adapter/examples/testing_example.py +0 -64
  74. mcp_proxy_adapter/models.py +0 -47
  75. mcp_proxy_adapter/registry.py +0 -439
  76. mcp_proxy_adapter/schema.py +0 -257
  77. mcp_proxy_adapter/testing_utils.py +0 -112
  78. mcp_proxy_adapter/validators/__init__.py +0 -1
  79. mcp_proxy_adapter/validators/docstring_validator.py +0 -75
  80. mcp_proxy_adapter/validators/metadata_validator.py +0 -76
  81. mcp_proxy_adapter-2.1.17.dist-info/METADATA +0 -376
  82. mcp_proxy_adapter-2.1.17.dist-info/RECORD +0 -30
  83. {mcp_proxy_adapter-2.1.17.dist-info → mcp_proxy_adapter-3.0.0.dist-info}/WHEEL +0 -0
  84. {mcp_proxy_adapter-2.1.17.dist-info → mcp_proxy_adapter-3.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,186 @@
1
+ """
2
+ Module for microservice configuration management.
3
+ """
4
+
5
+ import json
6
+ import os
7
+ from pathlib import Path
8
+ from typing import Any, Dict, Optional
9
+
10
+
11
+ class Config:
12
+ """
13
+ Configuration management class for the microservice.
14
+ Allows loading settings from configuration file and environment variables.
15
+ """
16
+
17
+ def __init__(self, config_path: Optional[str] = None):
18
+ """
19
+ Initialize configuration.
20
+
21
+ Args:
22
+ config_path: Path to configuration file. If not specified, "./config.json" is used.
23
+ """
24
+ self.config_path = config_path or "./config.json"
25
+ self.config_data: Dict[str, Any] = {}
26
+ self.load_config()
27
+
28
+ def load_config(self) -> None:
29
+ """
30
+ Load configuration from file and environment variables.
31
+ """
32
+ # Set default config values
33
+ self.config_data = {
34
+ "server": {
35
+ "host": "0.0.0.0",
36
+ "port": 8000,
37
+ "debug": False,
38
+ "log_level": "INFO"
39
+ },
40
+ "logging": {
41
+ "level": "INFO",
42
+ "file": None
43
+ }
44
+ }
45
+
46
+ # Try to load configuration from file
47
+ if os.path.exists(self.config_path):
48
+ try:
49
+ with open(self.config_path, 'r', encoding='utf-8') as f:
50
+ file_config = json.load(f)
51
+ self._update_nested_dict(self.config_data, file_config)
52
+ except Exception as e:
53
+ print(f"Error loading config from {self.config_path}: {e}")
54
+
55
+ # Load configuration from environment variables
56
+ self._load_env_variables()
57
+
58
+ def load_from_file(self, config_path: str) -> None:
59
+ """
60
+ Load configuration from the specified file.
61
+
62
+ Args:
63
+ config_path: Path to configuration file.
64
+ """
65
+ self.config_path = config_path
66
+ self.load_config()
67
+
68
+ def _load_env_variables(self) -> None:
69
+ """
70
+ Load configuration from environment variables.
71
+ Environment variables should be in format SERVICE_SECTION_KEY=value.
72
+ For example, SERVICE_SERVER_PORT=8080.
73
+ """
74
+ prefix = "SERVICE_"
75
+ for key, value in os.environ.items():
76
+ if key.startswith(prefix):
77
+ parts = key[len(prefix):].lower().split("_", 1)
78
+ if len(parts) == 2:
79
+ section, param = parts
80
+ if section not in self.config_data:
81
+ self.config_data[section] = {}
82
+ self.config_data[section][param] = self._convert_env_value(value)
83
+
84
+ def _convert_env_value(self, value: str) -> Any:
85
+ """
86
+ Convert environment variable value to appropriate type.
87
+
88
+ Args:
89
+ value: Value as string
90
+
91
+ Returns:
92
+ Converted value
93
+ """
94
+ # Try to convert to appropriate type
95
+ if value.lower() == "true":
96
+ return True
97
+ elif value.lower() == "false":
98
+ return False
99
+ elif value.isdigit():
100
+ return int(value)
101
+ else:
102
+ try:
103
+ return float(value)
104
+ except ValueError:
105
+ return value
106
+
107
+ def get(self, key: str, default: Any = None) -> Any:
108
+ """
109
+ Get configuration value for key.
110
+
111
+ Args:
112
+ key: Configuration key in format "section.param"
113
+ default: Default value if key not found
114
+
115
+ Returns:
116
+ Configuration value
117
+ """
118
+ parts = key.split(".")
119
+
120
+ # Get value from config
121
+ value = self.config_data
122
+ for part in parts:
123
+ if not isinstance(value, dict) or part not in value:
124
+ return default
125
+ value = value[part]
126
+
127
+ return value
128
+
129
+ def set(self, key: str, value: Any) -> None:
130
+ """
131
+ Set configuration value for key.
132
+
133
+ Args:
134
+ key: Configuration key in format "section.param"
135
+ value: Configuration value
136
+ """
137
+ parts = key.split(".")
138
+ if len(parts) == 1:
139
+ self.config_data[key] = value
140
+ else:
141
+ section = parts[0]
142
+ param = ".".join(parts[1:])
143
+
144
+ if section not in self.config_data:
145
+ self.config_data[section] = {}
146
+
147
+ current = self.config_data[section]
148
+ for part in parts[1:-1]:
149
+ if part not in current:
150
+ current[part] = {}
151
+ current = current[part]
152
+
153
+ current[parts[-1]] = value
154
+
155
+ def save(self, path: Optional[str] = None) -> None:
156
+ """
157
+ Save configuration to file.
158
+
159
+ Args:
160
+ path: Path to configuration file. If not specified, self.config_path is used.
161
+ """
162
+ save_path = path or self.config_path
163
+ with open(save_path, 'w', encoding='utf-8') as f:
164
+ json.dump(self.config_data, f, indent=2)
165
+
166
+ def _update_nested_dict(self, d: Dict, u: Dict) -> Dict:
167
+ """
168
+ Update nested dictionary recursively.
169
+
170
+ Args:
171
+ d: Dictionary to update
172
+ u: Dictionary with new values
173
+
174
+ Returns:
175
+ Updated dictionary
176
+ """
177
+ for k, v in u.items():
178
+ if isinstance(v, dict) and k in d and isinstance(d[k], dict):
179
+ self._update_nested_dict(d[k], v)
180
+ else:
181
+ d[k] = v
182
+ return d
183
+
184
+
185
+ # Singleton instance
186
+ config = Config()
@@ -0,0 +1,125 @@
1
+ """
2
+ Custom OpenAPI schema generator for MCP Microservice compatible with MCP-Proxy.
3
+ """
4
+ import json
5
+ from copy import deepcopy
6
+ from pathlib import Path
7
+ from typing import Any, Dict, List, Optional, Set, Type
8
+
9
+ from fastapi import FastAPI
10
+ from fastapi.openapi.utils import get_openapi
11
+
12
+ from mcp_proxy_adapter.commands.command_registry import registry
13
+ from mcp_proxy_adapter.commands.base import Command
14
+ from mcp_proxy_adapter.core.logging import logger
15
+
16
+
17
+ class CustomOpenAPIGenerator:
18
+ """
19
+ Custom OpenAPI schema generator for compatibility with MCP-Proxy.
20
+
21
+ This generator creates an OpenAPI schema that matches the format expected by MCP-Proxy,
22
+ enabling dynamic command loading and proper tool representation in AI models.
23
+ """
24
+
25
+ def __init__(self):
26
+ """Initialize the generator."""
27
+ self.base_schema_path = Path(__file__).parent / "schemas" / "openapi_schema.json"
28
+ self.base_schema = self._load_base_schema()
29
+
30
+ def _load_base_schema(self) -> Dict[str, Any]:
31
+ """
32
+ Load the base OpenAPI schema from file.
33
+
34
+ Returns:
35
+ Dict containing the base OpenAPI schema.
36
+ """
37
+ with open(self.base_schema_path, "r", encoding="utf-8") as f:
38
+ return json.load(f)
39
+
40
+ def _add_commands_to_schema(self, schema: Dict[str, Any]) -> None:
41
+ """
42
+ Add all registered commands to the OpenAPI schema.
43
+
44
+ Args:
45
+ schema: The OpenAPI schema to update.
46
+ """
47
+ # Get all commands from the registry
48
+ commands = registry.get_all_commands()
49
+
50
+ # Add command names to the CommandRequest enum
51
+ schema["components"]["schemas"]["CommandRequest"]["properties"]["command"]["enum"] = [
52
+ cmd for cmd in commands.keys()
53
+ ]
54
+
55
+ # Add command parameters to oneOf
56
+ params_refs = []
57
+
58
+ for name, cmd_class in commands.items():
59
+ # Create schema for command parameters
60
+ param_schema_name = f"{name.capitalize()}Params"
61
+ schema["components"]["schemas"][param_schema_name] = self._create_params_schema(cmd_class)
62
+
63
+ # Add to oneOf
64
+ params_refs.append({"$ref": f"#/components/schemas/{param_schema_name}"})
65
+
66
+ # Add null option for commands without parameters
67
+ params_refs.append({"type": "null"})
68
+
69
+ # Set oneOf for params
70
+ schema["components"]["schemas"]["CommandRequest"]["properties"]["params"]["oneOf"] = params_refs
71
+
72
+ def _create_params_schema(self, cmd_class: Type[Command]) -> Dict[str, Any]:
73
+ """
74
+ Create a schema for command parameters.
75
+
76
+ Args:
77
+ cmd_class: The command class.
78
+
79
+ Returns:
80
+ Dict containing the parameter schema.
81
+ """
82
+ # Get command schema
83
+ cmd_schema = cmd_class.get_schema()
84
+
85
+ # Add title and description
86
+ cmd_schema["title"] = f"Parameters for {cmd_class.name}"
87
+ cmd_schema["description"] = f"Parameters for the {cmd_class.name} command"
88
+
89
+ return cmd_schema
90
+
91
+ def generate(self) -> Dict[str, Any]:
92
+ """
93
+ Generate the complete OpenAPI schema compatible with MCP-Proxy.
94
+
95
+ Returns:
96
+ Dict containing the complete OpenAPI schema.
97
+ """
98
+ # Deep copy the base schema to avoid modifying it
99
+ schema = deepcopy(self.base_schema)
100
+
101
+ # Add commands to schema
102
+ self._add_commands_to_schema(schema)
103
+
104
+ logger.info(f"Generated OpenAPI schema with {len(registry.get_all_commands())} commands")
105
+
106
+ return schema
107
+
108
+
109
+ def custom_openapi(app: FastAPI) -> Dict[str, Any]:
110
+ """
111
+ Create a custom OpenAPI schema for the FastAPI application.
112
+
113
+ Args:
114
+ app: The FastAPI application.
115
+
116
+ Returns:
117
+ Dict containing the custom OpenAPI schema.
118
+ """
119
+ generator = CustomOpenAPIGenerator()
120
+ openapi_schema = generator.generate()
121
+
122
+ # Cache the schema
123
+ app.openapi_schema = openapi_schema
124
+
125
+ return openapi_schema
@@ -0,0 +1,109 @@
1
+ """
2
+ Framework module for creating microservices.
3
+ """
4
+
5
+ import os
6
+ import uvicorn
7
+ from typing import Any, Callable, Dict, List, Optional, Type, Union
8
+
9
+ from fastapi import FastAPI
10
+
11
+ from mcp_proxy_adapter.api.app import create_app
12
+ from mcp_proxy_adapter.commands.base import Command
13
+ from mcp_proxy_adapter.commands.command_registry import registry
14
+ from mcp_proxy_adapter.config import config
15
+ from mcp_proxy_adapter.core.logging import logger, setup_logging
16
+
17
+
18
+ class MicroService:
19
+ """
20
+ Main class for creating and managing microservices.
21
+ """
22
+
23
+ def __init__(
24
+ self,
25
+ title: str = "MicroService",
26
+ description: str = "JSON-RPC microservice",
27
+ version: str = "1.0.0",
28
+ config_path: Optional[str] = None,
29
+ log_level: str = "INFO",
30
+ ):
31
+ """
32
+ Initialize microservice.
33
+
34
+ Args:
35
+ title: Service title (displayed in docs)
36
+ description: Service description (displayed in docs)
37
+ version: Service version
38
+ config_path: Path to configuration file
39
+ log_level: Logging level
40
+ """
41
+ # Set configuration
42
+ if config_path and os.path.exists(config_path):
43
+ config.load_from_file(config_path)
44
+
45
+ # Set logging
46
+ setup_logging(log_level)
47
+
48
+ # Create FastAPI app
49
+ self.app = create_app()
50
+ self.app.title = title
51
+ self.app.description = description
52
+ self.app.version = version
53
+
54
+ logger.info(f"MicroService '{title}' v{version} initialized")
55
+
56
+ def register_command(self, command_class: Type[Command]) -> None:
57
+ """
58
+ Register command in the service.
59
+
60
+ Args:
61
+ command_class: Command class to register
62
+ """
63
+ registry.register(command_class)
64
+ logger.info(f"Command '{command_class.name if hasattr(command_class, 'name') else command_class.__name__}' registered")
65
+
66
+ def discover_commands(self, package_path: str) -> None:
67
+ """
68
+ Automatically discover and register commands in package.
69
+
70
+ Args:
71
+ package_path: Path to package with commands
72
+ """
73
+ registry.discover_commands(package_path)
74
+ logger.info(f"Commands discovered in package '{package_path}'")
75
+
76
+ def run(
77
+ self,
78
+ host: str = "0.0.0.0",
79
+ port: int = 8000,
80
+ reload: bool = False,
81
+ workers: int = 1,
82
+ ) -> None:
83
+ """
84
+ Run microservice.
85
+
86
+ Args:
87
+ host: Host to bind server
88
+ port: Port to bind server
89
+ reload: Enable auto-reload on code changes
90
+ workers: Number of worker processes
91
+ """
92
+ logger.info(f"Starting microservice on {host}:{port}")
93
+ uvicorn.run(
94
+ self.app,
95
+ host=host,
96
+ port=port,
97
+ reload=reload,
98
+ workers=workers,
99
+ log_level=config.get("logging.level", "info").lower()
100
+ )
101
+
102
+ def get_app(self) -> FastAPI:
103
+ """
104
+ Get FastAPI application instance.
105
+
106
+ Returns:
107
+ FastAPI application
108
+ """
109
+ return self.app