mcp-proxy-adapter 6.9.28__py3-none-any.whl → 6.9.30__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.
Potentially problematic release.
This version of mcp-proxy-adapter might be problematic. Click here for more details.
- mcp_proxy_adapter/__init__.py +10 -0
- mcp_proxy_adapter/__main__.py +8 -21
- mcp_proxy_adapter/api/app.py +10 -913
- mcp_proxy_adapter/api/core/__init__.py +18 -0
- mcp_proxy_adapter/api/core/app_factory.py +243 -0
- mcp_proxy_adapter/api/core/lifespan_manager.py +55 -0
- mcp_proxy_adapter/api/core/registration_manager.py +166 -0
- mcp_proxy_adapter/api/core/ssl_context_factory.py +88 -0
- mcp_proxy_adapter/api/handlers.py +78 -199
- mcp_proxy_adapter/api/middleware/__init__.py +1 -44
- mcp_proxy_adapter/api/middleware/base.py +0 -42
- mcp_proxy_adapter/api/middleware/command_permission_middleware.py +0 -85
- mcp_proxy_adapter/api/middleware/error_handling.py +1 -127
- mcp_proxy_adapter/api/middleware/factory.py +0 -94
- mcp_proxy_adapter/api/middleware/logging.py +0 -112
- mcp_proxy_adapter/api/middleware/performance.py +0 -35
- mcp_proxy_adapter/api/middleware/protocol_middleware.py +2 -98
- mcp_proxy_adapter/api/middleware/transport_middleware.py +0 -37
- mcp_proxy_adapter/api/middleware/unified_security.py +10 -10
- mcp_proxy_adapter/api/middleware/user_info_middleware.py +0 -118
- mcp_proxy_adapter/api/openapi/__init__.py +21 -0
- mcp_proxy_adapter/api/openapi/command_integration.py +105 -0
- mcp_proxy_adapter/api/openapi/openapi_generator.py +40 -0
- mcp_proxy_adapter/api/openapi/openapi_registry.py +62 -0
- mcp_proxy_adapter/api/openapi/schema_loader.py +116 -0
- mcp_proxy_adapter/api/schemas.py +0 -61
- mcp_proxy_adapter/api/tool_integration.py +0 -117
- mcp_proxy_adapter/api/tools.py +0 -46
- mcp_proxy_adapter/cli/__init__.py +12 -0
- mcp_proxy_adapter/cli/commands/__init__.py +15 -0
- mcp_proxy_adapter/cli/commands/client.py +100 -0
- mcp_proxy_adapter/cli/commands/config_generate.py +21 -0
- mcp_proxy_adapter/cli/commands/config_validate.py +36 -0
- mcp_proxy_adapter/cli/commands/generate.py +259 -0
- mcp_proxy_adapter/cli/commands/server.py +174 -0
- mcp_proxy_adapter/cli/commands/sets.py +128 -0
- mcp_proxy_adapter/cli/commands/testconfig.py +177 -0
- mcp_proxy_adapter/cli/examples/__init__.py +8 -0
- mcp_proxy_adapter/cli/examples/http_basic.py +82 -0
- mcp_proxy_adapter/cli/examples/https_token.py +96 -0
- mcp_proxy_adapter/cli/examples/mtls_roles.py +103 -0
- mcp_proxy_adapter/cli/main.py +63 -0
- mcp_proxy_adapter/cli/parser.py +324 -0
- mcp_proxy_adapter/cli/validators.py +231 -0
- mcp_proxy_adapter/client/jsonrpc_client.py +406 -0
- mcp_proxy_adapter/client/proxy.py +45 -0
- mcp_proxy_adapter/commands/__init__.py +44 -28
- mcp_proxy_adapter/commands/auth_validation_command.py +7 -344
- mcp_proxy_adapter/commands/base.py +19 -43
- mcp_proxy_adapter/commands/builtin_commands.py +0 -75
- mcp_proxy_adapter/commands/catalog/__init__.py +20 -0
- mcp_proxy_adapter/commands/catalog/catalog_loader.py +34 -0
- mcp_proxy_adapter/commands/catalog/catalog_manager.py +122 -0
- mcp_proxy_adapter/commands/catalog/catalog_syncer.py +149 -0
- mcp_proxy_adapter/commands/catalog/command_catalog.py +43 -0
- mcp_proxy_adapter/commands/catalog/dependency_manager.py +37 -0
- mcp_proxy_adapter/commands/catalog_manager.py +58 -928
- mcp_proxy_adapter/commands/cert_monitor_command.py +0 -88
- mcp_proxy_adapter/commands/certificate_management_command.py +0 -45
- mcp_proxy_adapter/commands/command_registry.py +172 -904
- mcp_proxy_adapter/commands/config_command.py +0 -28
- mcp_proxy_adapter/commands/dependency_container.py +1 -70
- mcp_proxy_adapter/commands/dependency_manager.py +0 -128
- mcp_proxy_adapter/commands/echo_command.py +0 -34
- mcp_proxy_adapter/commands/health_command.py +0 -3
- mcp_proxy_adapter/commands/help_command.py +0 -159
- mcp_proxy_adapter/commands/hooks.py +0 -137
- mcp_proxy_adapter/commands/key_management_command.py +0 -25
- mcp_proxy_adapter/commands/load_command.py +7 -78
- mcp_proxy_adapter/commands/plugins_command.py +0 -16
- mcp_proxy_adapter/commands/protocol_management_command.py +0 -28
- mcp_proxy_adapter/commands/proxy_registration_command.py +0 -88
- mcp_proxy_adapter/commands/queue_commands.py +750 -0
- mcp_proxy_adapter/commands/registration_status_command.py +0 -43
- mcp_proxy_adapter/commands/registry/__init__.py +18 -0
- mcp_proxy_adapter/commands/registry/command_info.py +103 -0
- mcp_proxy_adapter/commands/registry/command_loader.py +207 -0
- mcp_proxy_adapter/commands/registry/command_manager.py +119 -0
- mcp_proxy_adapter/commands/registry/command_registry.py +217 -0
- mcp_proxy_adapter/commands/reload_command.py +0 -80
- mcp_proxy_adapter/commands/result.py +25 -77
- mcp_proxy_adapter/commands/role_test_command.py +0 -44
- mcp_proxy_adapter/commands/roles_management_command.py +0 -199
- mcp_proxy_adapter/commands/security_command.py +0 -30
- mcp_proxy_adapter/commands/settings_command.py +0 -68
- mcp_proxy_adapter/commands/ssl_setup_command.py +0 -42
- mcp_proxy_adapter/commands/token_management_command.py +0 -1
- mcp_proxy_adapter/commands/transport_management_command.py +0 -20
- mcp_proxy_adapter/commands/unload_command.py +0 -71
- mcp_proxy_adapter/config.py +15 -626
- mcp_proxy_adapter/core/__init__.py +5 -39
- mcp_proxy_adapter/core/app_factory.py +14 -36
- mcp_proxy_adapter/core/app_runner.py +0 -27
- mcp_proxy_adapter/core/auth_validator.py +1 -93
- mcp_proxy_adapter/core/certificate/__init__.py +20 -0
- mcp_proxy_adapter/core/certificate/certificate_creator.py +371 -0
- mcp_proxy_adapter/core/certificate/certificate_extractor.py +183 -0
- mcp_proxy_adapter/core/certificate/certificate_utils.py +249 -0
- mcp_proxy_adapter/core/certificate/certificate_validator.py +110 -0
- mcp_proxy_adapter/core/certificate/ssl_context_manager.py +70 -0
- mcp_proxy_adapter/core/certificate_utils.py +64 -903
- mcp_proxy_adapter/core/client.py +10 -9
- mcp_proxy_adapter/core/client_manager.py +0 -19
- mcp_proxy_adapter/core/client_security.py +0 -2
- mcp_proxy_adapter/core/config/__init__.py +18 -0
- mcp_proxy_adapter/core/config/config.py +195 -0
- mcp_proxy_adapter/core/config/config_factory.py +22 -0
- mcp_proxy_adapter/core/config/config_loader.py +66 -0
- mcp_proxy_adapter/core/config/feature_manager.py +31 -0
- mcp_proxy_adapter/core/config/simple_config.py +112 -0
- mcp_proxy_adapter/core/config/simple_config_generator.py +50 -0
- mcp_proxy_adapter/core/config/simple_config_validator.py +96 -0
- mcp_proxy_adapter/core/config_converter.py +0 -186
- mcp_proxy_adapter/core/config_validator.py +96 -1238
- mcp_proxy_adapter/core/errors.py +7 -42
- mcp_proxy_adapter/core/job_manager.py +54 -0
- mcp_proxy_adapter/core/logging.py +2 -22
- mcp_proxy_adapter/core/mtls_asgi.py +0 -20
- mcp_proxy_adapter/core/mtls_asgi_app.py +0 -12
- mcp_proxy_adapter/core/mtls_proxy.py +0 -80
- mcp_proxy_adapter/core/mtls_server.py +3 -173
- mcp_proxy_adapter/core/protocol_manager.py +1 -191
- mcp_proxy_adapter/core/proxy/__init__.py +22 -0
- mcp_proxy_adapter/core/proxy/auth_manager.py +27 -0
- mcp_proxy_adapter/core/proxy/proxy_registration_manager.py +137 -0
- mcp_proxy_adapter/core/proxy/registration_client.py +60 -0
- mcp_proxy_adapter/core/proxy/ssl_manager.py +101 -0
- mcp_proxy_adapter/core/proxy_client.py +0 -1
- mcp_proxy_adapter/core/proxy_registration.py +36 -913
- mcp_proxy_adapter/core/role_utils.py +0 -308
- mcp_proxy_adapter/core/security_adapter.py +1 -36
- mcp_proxy_adapter/core/security_factory.py +1 -150
- mcp_proxy_adapter/core/security_integration.py +0 -33
- mcp_proxy_adapter/core/server_adapter.py +1 -40
- mcp_proxy_adapter/core/server_engine.py +2 -173
- mcp_proxy_adapter/core/settings.py +0 -127
- mcp_proxy_adapter/core/signal_handler.py +0 -65
- mcp_proxy_adapter/core/ssl_utils.py +19 -137
- mcp_proxy_adapter/core/transport_manager.py +0 -151
- mcp_proxy_adapter/core/unified_config_adapter.py +1 -193
- mcp_proxy_adapter/core/utils.py +1 -182
- mcp_proxy_adapter/core/validation/__init__.py +21 -0
- mcp_proxy_adapter/core/validation/config_validator.py +211 -0
- mcp_proxy_adapter/core/validation/file_validator.py +73 -0
- mcp_proxy_adapter/core/validation/protocol_validator.py +191 -0
- mcp_proxy_adapter/core/validation/security_validator.py +58 -0
- mcp_proxy_adapter/core/validation/validation_result.py +27 -0
- mcp_proxy_adapter/custom_openapi.py +33 -652
- mcp_proxy_adapter/examples/bugfix_certificate_config.py +0 -23
- mcp_proxy_adapter/examples/check_config.py +0 -2
- mcp_proxy_adapter/examples/client_usage_example.py +164 -0
- mcp_proxy_adapter/examples/config_builder.py +13 -2
- mcp_proxy_adapter/examples/config_cli.py +0 -1
- mcp_proxy_adapter/examples/create_test_configs.py +0 -46
- mcp_proxy_adapter/examples/debug_request_state.py +0 -1
- mcp_proxy_adapter/examples/full_application/commands/custom_echo_command.py +0 -47
- mcp_proxy_adapter/examples/full_application/commands/dynamic_calculator_command.py +0 -45
- mcp_proxy_adapter/examples/full_application/commands/echo_command.py +0 -12
- mcp_proxy_adapter/examples/full_application/commands/help_command.py +0 -12
- mcp_proxy_adapter/examples/full_application/commands/list_command.py +0 -7
- mcp_proxy_adapter/examples/full_application/hooks/__init__.py +0 -2
- mcp_proxy_adapter/examples/full_application/hooks/application_hooks.py +0 -59
- mcp_proxy_adapter/examples/full_application/hooks/builtin_command_hooks.py +0 -54
- mcp_proxy_adapter/examples/full_application/main.py +186 -150
- mcp_proxy_adapter/examples/full_application/proxy_endpoints.py +0 -107
- mcp_proxy_adapter/examples/full_application/test_minimal_server.py +0 -24
- mcp_proxy_adapter/examples/full_application/test_server.py +0 -58
- mcp_proxy_adapter/examples/generate_config.py +65 -11
- mcp_proxy_adapter/examples/queue_demo_simple.py +632 -0
- mcp_proxy_adapter/examples/queue_integration_example.py +578 -0
- mcp_proxy_adapter/examples/queue_server_demo.py +82 -0
- mcp_proxy_adapter/examples/queue_server_example.py +85 -0
- mcp_proxy_adapter/examples/queue_server_simple.py +173 -0
- mcp_proxy_adapter/examples/required_certificates.py +0 -2
- mcp_proxy_adapter/examples/run_full_test_suite.py +0 -29
- mcp_proxy_adapter/examples/run_proxy_server.py +31 -71
- mcp_proxy_adapter/examples/run_security_tests_fixed.py +0 -27
- mcp_proxy_adapter/examples/security_test/__init__.py +18 -0
- mcp_proxy_adapter/examples/security_test/auth_manager.py +14 -0
- mcp_proxy_adapter/examples/security_test/ssl_context_manager.py +28 -0
- mcp_proxy_adapter/examples/security_test/test_client.py +159 -0
- mcp_proxy_adapter/examples/security_test/test_result.py +22 -0
- mcp_proxy_adapter/examples/security_test_client.py +24 -1075
- mcp_proxy_adapter/examples/setup/__init__.py +24 -0
- mcp_proxy_adapter/examples/setup/certificate_manager.py +215 -0
- mcp_proxy_adapter/examples/setup/config_generator.py +12 -0
- mcp_proxy_adapter/examples/setup/config_validator.py +118 -0
- mcp_proxy_adapter/examples/setup/environment_setup.py +62 -0
- mcp_proxy_adapter/examples/setup/test_files_generator.py +10 -0
- mcp_proxy_adapter/examples/setup/test_runner.py +89 -0
- mcp_proxy_adapter/examples/setup_test_environment.py +133 -1425
- mcp_proxy_adapter/examples/test_config.py +0 -3
- mcp_proxy_adapter/examples/test_config_builder.py +25 -405
- mcp_proxy_adapter/examples/test_examples.py +0 -1
- mcp_proxy_adapter/examples/test_framework_complete.py +0 -2
- mcp_proxy_adapter/examples/test_mcp_server.py +0 -1
- mcp_proxy_adapter/examples/test_protocol_examples.py +0 -1
- mcp_proxy_adapter/examples/universal_client.py +0 -6
- mcp_proxy_adapter/examples/update_config_certificates.py +0 -1
- mcp_proxy_adapter/examples/validate_generator_compatibility.py +0 -1
- mcp_proxy_adapter/examples/validate_generator_compatibility_simple.py +0 -187
- mcp_proxy_adapter/integrations/__init__.py +25 -0
- mcp_proxy_adapter/integrations/queuemgr_integration.py +462 -0
- mcp_proxy_adapter/main.py +70 -62
- mcp_proxy_adapter/openapi.py +0 -22
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.30.dist-info}/METADATA +2 -1
- mcp_proxy_adapter-6.9.30.dist-info/RECORD +235 -0
- {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.30.dist-info}/entry_points.txt +1 -1
- mcp_proxy_adapter-6.9.28.dist-info/RECORD +0 -149
- {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.30.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-6.9.28.dist-info → mcp_proxy_adapter-6.9.30.dist-info}/top_level.txt +0 -0
|
@@ -130,121 +130,3 @@ class UserInfoMiddleware(BaseHTTPMiddleware):
|
|
|
130
130
|
else:
|
|
131
131
|
get_global_logger().info("ℹ️ User info middleware initialized with mcp_security_framework (fallback enabled)")
|
|
132
132
|
|
|
133
|
-
async def dispatch(
|
|
134
|
-
self, request: Request, call_next: Callable[[Request], Awaitable[Response]]
|
|
135
|
-
) -> Response:
|
|
136
|
-
"""
|
|
137
|
-
Process request and set user info in request.state.
|
|
138
|
-
|
|
139
|
-
Args:
|
|
140
|
-
request: Request object
|
|
141
|
-
call_next: Next handler
|
|
142
|
-
|
|
143
|
-
Returns:
|
|
144
|
-
Response object
|
|
145
|
-
"""
|
|
146
|
-
get_global_logger().debug(f"🔍 UserInfoMiddleware.dispatch START - {request.method} {request.url.path}")
|
|
147
|
-
get_global_logger().debug(f"🔍 UserInfoMiddleware - Headers: {dict(request.headers)}")
|
|
148
|
-
get_global_logger().debug(f"🔍 UserInfoMiddleware - AuthManager available: {self.auth_manager is not None}")
|
|
149
|
-
get_global_logger().debug(f"🔍 UserInfoMiddleware - Security available: {self._security_available}")
|
|
150
|
-
|
|
151
|
-
# Extract API key from headers
|
|
152
|
-
api_key = request.headers.get("X-API-Key")
|
|
153
|
-
get_global_logger().debug(f"🔍 UserInfoMiddleware - API Key: {api_key[:8] + '...' if api_key else 'None'}")
|
|
154
|
-
if api_key:
|
|
155
|
-
if self.auth_manager and self._security_available:
|
|
156
|
-
try:
|
|
157
|
-
# Use mcp_security_framework AuthManager
|
|
158
|
-
auth_result = self.auth_manager.authenticate_api_key(api_key)
|
|
159
|
-
|
|
160
|
-
if auth_result.is_valid:
|
|
161
|
-
# Set user info from AuthManager result
|
|
162
|
-
request.state.user = {
|
|
163
|
-
"id": api_key,
|
|
164
|
-
"role": (
|
|
165
|
-
auth_result.roles[0] if auth_result.roles else "guest"
|
|
166
|
-
),
|
|
167
|
-
"roles": auth_result.roles or ["guest"],
|
|
168
|
-
"permissions": getattr(
|
|
169
|
-
auth_result, "permissions", ["read"]
|
|
170
|
-
),
|
|
171
|
-
}
|
|
172
|
-
get_global_logger().debug(
|
|
173
|
-
f"✅ Authenticated user with "
|
|
174
|
-
f"mcp_security_framework: "
|
|
175
|
-
f"{request.state.user}"
|
|
176
|
-
)
|
|
177
|
-
else:
|
|
178
|
-
# Authentication failed
|
|
179
|
-
request.state.user = {
|
|
180
|
-
"id": None,
|
|
181
|
-
"role": "guest",
|
|
182
|
-
"roles": ["guest"],
|
|
183
|
-
"permissions": ["read"],
|
|
184
|
-
}
|
|
185
|
-
get_global_logger().debug(
|
|
186
|
-
f"❌ Authentication failed for API key: "
|
|
187
|
-
f"{api_key[:8]}..."
|
|
188
|
-
)
|
|
189
|
-
except Exception as e:
|
|
190
|
-
get_global_logger().warning(
|
|
191
|
-
f"⚠️ AuthManager error: {e}, " f"falling back to basic auth"
|
|
192
|
-
)
|
|
193
|
-
self._security_available = False
|
|
194
|
-
|
|
195
|
-
if not self._security_available:
|
|
196
|
-
# Fallback to basic API key handling
|
|
197
|
-
api_keys_dict = getattr(self, "api_keys", {})
|
|
198
|
-
# Find role by API key value (not key)
|
|
199
|
-
user_role = None
|
|
200
|
-
for role, key_value in api_keys_dict.items():
|
|
201
|
-
if key_value == api_key:
|
|
202
|
-
user_role = role
|
|
203
|
-
break
|
|
204
|
-
|
|
205
|
-
if user_role:
|
|
206
|
-
# Get permissions for this role from roles file if available
|
|
207
|
-
role_permissions = ["read"] # default permissions
|
|
208
|
-
if (
|
|
209
|
-
hasattr(self, "roles_config")
|
|
210
|
-
and self.roles_config
|
|
211
|
-
and user_role in self.roles_config
|
|
212
|
-
):
|
|
213
|
-
role_permissions = self.roles_config[user_role].get(
|
|
214
|
-
"permissions", ["read"]
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
# Set user info in request.state
|
|
218
|
-
request.state.user = {
|
|
219
|
-
"id": api_key,
|
|
220
|
-
"role": user_role,
|
|
221
|
-
"roles": [user_role],
|
|
222
|
-
"permissions": role_permissions,
|
|
223
|
-
}
|
|
224
|
-
get_global_logger().debug(
|
|
225
|
-
f"✅ User authenticated with API key: "
|
|
226
|
-
f"{api_key[:8]}..."
|
|
227
|
-
)
|
|
228
|
-
else:
|
|
229
|
-
# API key not found
|
|
230
|
-
request.state.user = {
|
|
231
|
-
"id": None,
|
|
232
|
-
"role": "guest",
|
|
233
|
-
"roles": ["guest"],
|
|
234
|
-
"permissions": ["read"],
|
|
235
|
-
}
|
|
236
|
-
get_global_logger().debug(f"❌ API key not found: {api_key[:8]}...")
|
|
237
|
-
else:
|
|
238
|
-
# No API key provided - guest access
|
|
239
|
-
request.state.user = {
|
|
240
|
-
"id": None,
|
|
241
|
-
"role": "guest",
|
|
242
|
-
"roles": ["guest"],
|
|
243
|
-
"permissions": ["read"],
|
|
244
|
-
}
|
|
245
|
-
get_global_logger().debug("ℹ️ No API key provided, using guest access")
|
|
246
|
-
|
|
247
|
-
get_global_logger().debug(f"🔍 UserInfoMiddleware - About to call next handler")
|
|
248
|
-
response = await call_next(request)
|
|
249
|
-
get_global_logger().debug(f"🔍 UserInfoMiddleware - Next handler completed with status: {response.status_code}")
|
|
250
|
-
return response
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
OpenAPI schema generation package for MCP Proxy Adapter.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .openapi_generator import CustomOpenAPIGenerator
|
|
9
|
+
from .schema_loader import SchemaLoader
|
|
10
|
+
from .command_integration import CommandIntegrator
|
|
11
|
+
from .openapi_registry import OpenAPIRegistry
|
|
12
|
+
from .custom_openapi import custom_openapi, custom_openapi_with_fallback
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"CustomOpenAPIGenerator",
|
|
16
|
+
"SchemaLoader",
|
|
17
|
+
"CommandIntegrator",
|
|
18
|
+
"OpenAPIRegistry",
|
|
19
|
+
"custom_openapi",
|
|
20
|
+
"custom_openapi_with_fallback",
|
|
21
|
+
]
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
Command integration utilities for OpenAPI generation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Dict, Any, Type
|
|
9
|
+
|
|
10
|
+
from mcp_proxy_adapter.commands.command_registry import registry
|
|
11
|
+
from mcp_proxy_adapter.commands.base import Command
|
|
12
|
+
from mcp_proxy_adapter.core.logging import get_global_logger
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CommandIntegrator:
|
|
16
|
+
"""Integrator for adding commands to OpenAPI schema."""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
"""Initialize command integrator."""
|
|
20
|
+
self.logger = get_global_logger()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _create_params_schema(self, cmd_class: Type[Command]) -> Dict[str, Any]:
|
|
24
|
+
"""
|
|
25
|
+
Create a schema for command parameters.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
cmd_class: The command class to create schema for.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Dict containing the parameter schema.
|
|
32
|
+
"""
|
|
33
|
+
try:
|
|
34
|
+
# Get the command schema
|
|
35
|
+
schema = cmd_class.get_schema()
|
|
36
|
+
|
|
37
|
+
if not schema or "properties" not in schema:
|
|
38
|
+
return {"type": "object", "properties": {}}
|
|
39
|
+
|
|
40
|
+
# Convert to OpenAPI format
|
|
41
|
+
openapi_schema = {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"properties": {},
|
|
44
|
+
"required": schema.get("required", [])
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# Convert properties
|
|
48
|
+
for prop_name, prop_schema in schema["properties"].items():
|
|
49
|
+
openapi_schema["properties"][prop_name] = self._convert_property_schema(prop_schema)
|
|
50
|
+
|
|
51
|
+
return openapi_schema
|
|
52
|
+
|
|
53
|
+
except Exception as e:
|
|
54
|
+
self.logger.warning(f"Failed to create params schema for {cmd_class.__name__}: {e}")
|
|
55
|
+
return {"type": "object", "properties": {}}
|
|
56
|
+
|
|
57
|
+
def _convert_property_schema(self, prop_schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
58
|
+
"""
|
|
59
|
+
Convert property schema to OpenAPI format.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
prop_schema: Property schema to convert.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
OpenAPI property schema.
|
|
66
|
+
"""
|
|
67
|
+
openapi_prop = {}
|
|
68
|
+
|
|
69
|
+
# Handle type
|
|
70
|
+
if "type" in prop_schema:
|
|
71
|
+
openapi_prop["type"] = prop_schema["type"]
|
|
72
|
+
|
|
73
|
+
# Handle description
|
|
74
|
+
if "description" in prop_schema:
|
|
75
|
+
openapi_prop["description"] = prop_schema["description"]
|
|
76
|
+
|
|
77
|
+
# Handle default
|
|
78
|
+
if "default" in prop_schema:
|
|
79
|
+
openapi_prop["default"] = prop_schema["default"]
|
|
80
|
+
|
|
81
|
+
# Handle enum
|
|
82
|
+
if "enum" in prop_schema:
|
|
83
|
+
openapi_prop["enum"] = prop_schema["enum"]
|
|
84
|
+
|
|
85
|
+
# Handle minimum/maximum
|
|
86
|
+
if "minimum" in prop_schema:
|
|
87
|
+
openapi_prop["minimum"] = prop_schema["minimum"]
|
|
88
|
+
if "maximum" in prop_schema:
|
|
89
|
+
openapi_prop["maximum"] = prop_schema["maximum"]
|
|
90
|
+
|
|
91
|
+
# Handle minLength/maxLength
|
|
92
|
+
if "minLength" in prop_schema:
|
|
93
|
+
openapi_prop["minLength"] = prop_schema["minLength"]
|
|
94
|
+
if "maxLength" in prop_schema:
|
|
95
|
+
openapi_prop["maxLength"] = prop_schema["maxLength"]
|
|
96
|
+
|
|
97
|
+
# Handle pattern
|
|
98
|
+
if "pattern" in prop_schema:
|
|
99
|
+
openapi_prop["pattern"] = prop_schema["pattern"]
|
|
100
|
+
|
|
101
|
+
# Handle items for arrays
|
|
102
|
+
if "items" in prop_schema:
|
|
103
|
+
openapi_prop["items"] = self._convert_property_schema(prop_schema["items"])
|
|
104
|
+
|
|
105
|
+
return openapi_prop
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
Main OpenAPI generator for MCP Proxy Adapter.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from copy import deepcopy
|
|
9
|
+
from typing import Any, Dict, Optional
|
|
10
|
+
|
|
11
|
+
from fastapi import FastAPI
|
|
12
|
+
|
|
13
|
+
from mcp_proxy_adapter.core.logging import get_global_logger
|
|
14
|
+
from .schema_loader import SchemaLoader
|
|
15
|
+
from .command_integration import CommandIntegrator
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CustomOpenAPIGenerator:
|
|
19
|
+
"""
|
|
20
|
+
Custom OpenAPI schema generator for compatibility with MCP-Proxy.
|
|
21
|
+
|
|
22
|
+
EN:
|
|
23
|
+
This generator creates an OpenAPI schema that matches the format expected by MCP-Proxy,
|
|
24
|
+
enabling dynamic command loading and proper tool representation in AI models.
|
|
25
|
+
Allows overriding title, description, and version for schema customization.
|
|
26
|
+
|
|
27
|
+
RU:
|
|
28
|
+
Кастомный генератор схемы OpenAPI для совместимости с MCP-Proxy.
|
|
29
|
+
Позволяет создавать схему OpenAPI в формате, ожидаемом MCP-Proxy,
|
|
30
|
+
с возможностью динамической подгрузки команд и корректного отображения инструментов для AI-моделей.
|
|
31
|
+
Поддерживает переопределение title, description и version для кастомизации схемы.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self):
|
|
35
|
+
"""Initialize the generator."""
|
|
36
|
+
self.logger = get_global_logger()
|
|
37
|
+
self.schema_loader = SchemaLoader()
|
|
38
|
+
self.command_integrator = CommandIntegrator()
|
|
39
|
+
self.base_schema = self.schema_loader.load_base_schema()
|
|
40
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
Registry for OpenAPI generators.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Dict, Callable, Optional, List
|
|
9
|
+
|
|
10
|
+
from mcp_proxy_adapter.core.logging import get_global_logger
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class OpenAPIRegistry:
|
|
14
|
+
"""Registry for OpenAPI generators."""
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
"""Initialize OpenAPI registry."""
|
|
18
|
+
self.logger = get_global_logger()
|
|
19
|
+
self._generators: Dict[str, Callable] = {}
|
|
20
|
+
|
|
21
|
+
def register_generator(self, name: str, generator: Callable) -> None:
|
|
22
|
+
"""
|
|
23
|
+
Register an OpenAPI generator.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
name: Generator name
|
|
27
|
+
generator: Generator function
|
|
28
|
+
"""
|
|
29
|
+
self._generators[name] = generator
|
|
30
|
+
self.logger.debug(f"Registered OpenAPI generator: {name}")
|
|
31
|
+
|
|
32
|
+
def get_generator(self, name: str) -> Optional[Callable]:
|
|
33
|
+
"""
|
|
34
|
+
Get an OpenAPI generator by name.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
name: Generator name
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Generator function or None if not found
|
|
41
|
+
"""
|
|
42
|
+
return self._generators.get(name)
|
|
43
|
+
|
|
44
|
+
def list_generators(self) -> List[str]:
|
|
45
|
+
"""
|
|
46
|
+
List all registered generators.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
List of generator names
|
|
50
|
+
"""
|
|
51
|
+
return list(self._generators.keys())
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# Global registry instance
|
|
56
|
+
_registry = OpenAPIRegistry()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Author: Vasiliy Zdanovskiy
|
|
3
|
+
email: vasilyvz@gmail.com
|
|
4
|
+
|
|
5
|
+
Schema loading utilities for OpenAPI generation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Dict, Any
|
|
11
|
+
|
|
12
|
+
from mcp_proxy_adapter.core.logging import get_global_logger
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SchemaLoader:
|
|
16
|
+
"""Loader for OpenAPI base schemas."""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
"""Initialize schema loader."""
|
|
20
|
+
self.logger = get_global_logger()
|
|
21
|
+
self.base_schema_path = (
|
|
22
|
+
Path(__file__).parent.parent.parent / "schemas" / "openapi_schema.json"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_fallback_schema(self) -> Dict[str, Any]:
|
|
27
|
+
"""
|
|
28
|
+
Get a fallback OpenAPI schema when the base schema file is not available.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Dict containing a basic OpenAPI schema.
|
|
32
|
+
"""
|
|
33
|
+
return {
|
|
34
|
+
"openapi": "3.0.2",
|
|
35
|
+
"info": {
|
|
36
|
+
"title": "MCP Microservice API",
|
|
37
|
+
"description": "API для выполнения команд микросервиса",
|
|
38
|
+
"version": "1.0.0"
|
|
39
|
+
},
|
|
40
|
+
"paths": {
|
|
41
|
+
"/cmd": {
|
|
42
|
+
"post": {
|
|
43
|
+
"summary": "Execute Command",
|
|
44
|
+
"description": "Executes a command via JSON-RPC protocol.",
|
|
45
|
+
"operationId": "execute_command",
|
|
46
|
+
"requestBody": {
|
|
47
|
+
"content": {
|
|
48
|
+
"application/json": {
|
|
49
|
+
"schema": {
|
|
50
|
+
"oneOf": [
|
|
51
|
+
{ "$ref": "#/components/schemas/CommandRequest" },
|
|
52
|
+
{ "$ref": "#/components/schemas/JsonRpcRequest" }
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"required": True
|
|
58
|
+
},
|
|
59
|
+
"responses": {
|
|
60
|
+
"200": {
|
|
61
|
+
"description": "Successful Response",
|
|
62
|
+
"content": {
|
|
63
|
+
"application/json": {
|
|
64
|
+
"schema": {
|
|
65
|
+
"oneOf": [
|
|
66
|
+
{ "$ref": "#/components/schemas/CommandResponse" },
|
|
67
|
+
{ "$ref": "#/components/schemas/JsonRpcResponse" }
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"components": {
|
|
78
|
+
"schemas": {
|
|
79
|
+
"CommandRequest": {
|
|
80
|
+
"type": "object",
|
|
81
|
+
"properties": {
|
|
82
|
+
"command": {"type": "string"},
|
|
83
|
+
"params": {"type": "object"}
|
|
84
|
+
},
|
|
85
|
+
"required": ["command"]
|
|
86
|
+
},
|
|
87
|
+
"CommandResponse": {
|
|
88
|
+
"type": "object",
|
|
89
|
+
"properties": {
|
|
90
|
+
"success": {"type": "boolean"},
|
|
91
|
+
"data": {"type": "object"},
|
|
92
|
+
"error": {"type": "string"}
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"JsonRpcRequest": {
|
|
96
|
+
"type": "object",
|
|
97
|
+
"properties": {
|
|
98
|
+
"jsonrpc": {"type": "string", "enum": ["2.0"]},
|
|
99
|
+
"method": {"type": "string"},
|
|
100
|
+
"params": {"type": "object"},
|
|
101
|
+
"id": {"type": "string"}
|
|
102
|
+
},
|
|
103
|
+
"required": ["jsonrpc", "method", "id"]
|
|
104
|
+
},
|
|
105
|
+
"JsonRpcResponse": {
|
|
106
|
+
"type": "object",
|
|
107
|
+
"properties": {
|
|
108
|
+
"jsonrpc": {"type": "string", "enum": ["2.0"]},
|
|
109
|
+
"result": {"type": "object"},
|
|
110
|
+
"error": {"type": "object"},
|
|
111
|
+
"id": {"type": "string"}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
mcp_proxy_adapter/api/schemas.py
CHANGED
|
@@ -202,67 +202,6 @@ class APIToolDescription:
|
|
|
202
202
|
return description
|
|
203
203
|
|
|
204
204
|
@classmethod
|
|
205
|
-
def generate_tool_description_text(cls, name: str, registry) -> str:
|
|
206
|
-
"""
|
|
207
|
-
Генерирует текстовое описание инструмента API для документации.
|
|
208
|
-
|
|
209
|
-
Args:
|
|
210
|
-
name: Имя инструмента API
|
|
211
|
-
registry: Реестр команд
|
|
212
|
-
|
|
213
|
-
Returns:
|
|
214
|
-
Текстовое описание инструмента в формате markdown
|
|
215
|
-
"""
|
|
216
|
-
tool_data = cls.generate_tool_description(name, registry)
|
|
217
|
-
|
|
218
|
-
# Формируем заголовок и базовое описание
|
|
219
|
-
text = f"# Инструмент {tool_data['name']}\n\n"
|
|
220
|
-
text += f"{tool_data['description']}\n\n"
|
|
221
|
-
|
|
222
|
-
# Список доступных команд
|
|
223
|
-
text += "## Доступные команды\n\n"
|
|
224
|
-
for cmd_name, cmd_info in tool_data["supported_commands"].items():
|
|
225
|
-
text += f"### {cmd_name}\n\n"
|
|
226
|
-
text += f"{cmd_info['description']}\n\n"
|
|
227
|
-
|
|
228
|
-
# Информация о параметрах
|
|
229
|
-
if cmd_info["params"]:
|
|
230
|
-
text += "#### Параметры:\n\n"
|
|
231
|
-
for param_name, param_info in cmd_info["params"].items():
|
|
232
|
-
required_mark = (
|
|
233
|
-
"**обязательный**" if param_info["required"] else "опциональный"
|
|
234
|
-
)
|
|
235
|
-
text += f"- `{param_name}` ({param_info['type']}, {required_mark}): {param_info['description']}\n"
|
|
236
|
-
text += "\n"
|
|
237
|
-
|
|
238
|
-
# Примеры использования
|
|
239
|
-
cmd_examples = [
|
|
240
|
-
ex for ex in tool_data["examples"] if ex["command"] == cmd_name
|
|
241
|
-
]
|
|
242
|
-
if cmd_examples:
|
|
243
|
-
text += "#### Примеры:\n\n"
|
|
244
|
-
for i, example in enumerate(cmd_examples):
|
|
245
|
-
text += f"**Пример {i+1}**: {example['description']}\n"
|
|
246
|
-
text += "```json\n"
|
|
247
|
-
text += "{\n"
|
|
248
|
-
text += f' "command": "{example["command"]}",\n'
|
|
249
|
-
if example["params"]:
|
|
250
|
-
text += ' "params": {\n'
|
|
251
|
-
params_str = []
|
|
252
|
-
for p_name, p_value in example["params"].items():
|
|
253
|
-
if isinstance(p_value, str):
|
|
254
|
-
p_str = f' "{p_name}": "{p_value}"'
|
|
255
|
-
else:
|
|
256
|
-
p_str = f' "{p_name}": {p_value}'
|
|
257
|
-
params_str.append(p_str)
|
|
258
|
-
text += ",\n".join(params_str)
|
|
259
|
-
text += "\n }\n"
|
|
260
|
-
else:
|
|
261
|
-
text += ' "params": {}\n'
|
|
262
|
-
text += "}\n"
|
|
263
|
-
text += "```\n\n"
|
|
264
|
-
|
|
265
|
-
return text
|
|
266
205
|
|
|
267
206
|
@classmethod
|
|
268
207
|
def _simplify_type(cls, type_str: str) -> str:
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
и других API интерфейсов.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from typing import Any, Dict, List, Optional, Union
|
|
10
9
|
import json
|
|
11
10
|
import logging
|
|
12
11
|
|
|
@@ -79,72 +78,8 @@ class ToolIntegration:
|
|
|
79
78
|
return schema
|
|
80
79
|
|
|
81
80
|
@classmethod
|
|
82
|
-
def generate_tool_documentation(
|
|
83
|
-
cls, tool_name: str, registry: CommandRegistry, format: str = "markdown"
|
|
84
|
-
) -> str:
|
|
85
|
-
"""
|
|
86
|
-
Генерирует документацию по инструменту API в заданном формате.
|
|
87
|
-
|
|
88
|
-
Args:
|
|
89
|
-
tool_name: Имя инструмента API
|
|
90
|
-
registry: Реестр команд
|
|
91
|
-
format: Формат документации (markdown, html)
|
|
92
|
-
|
|
93
|
-
Returns:
|
|
94
|
-
Строка с документацией в заданном формате
|
|
95
|
-
"""
|
|
96
|
-
if format.lower() == "markdown":
|
|
97
|
-
return APIToolDescription.generate_tool_description_text(
|
|
98
|
-
tool_name, registry
|
|
99
|
-
)
|
|
100
|
-
elif format.lower() == "html":
|
|
101
|
-
# Преобразуем markdown в HTML (в реальном проекте здесь будет
|
|
102
|
-
# использоваться библиотека для конвертации markdown в HTML)
|
|
103
|
-
markdown = APIToolDescription.generate_tool_description_text(
|
|
104
|
-
tool_name, registry
|
|
105
|
-
)
|
|
106
|
-
# Простая конвертация для примера
|
|
107
|
-
body = markdown.replace("#", "<h1>").replace("\n\n", "</p><p>")
|
|
108
|
-
html = f"<html><body>{body}</body></html>"
|
|
109
|
-
return html
|
|
110
|
-
else:
|
|
111
|
-
# По умолчанию возвращаем markdown
|
|
112
|
-
return APIToolDescription.generate_tool_description_text(
|
|
113
|
-
tool_name, registry
|
|
114
|
-
)
|
|
115
81
|
|
|
116
82
|
@classmethod
|
|
117
|
-
def register_external_tools(
|
|
118
|
-
cls, registry: CommandRegistry, tool_names: List[str]
|
|
119
|
-
) -> Dict[str, Dict[str, Any]]:
|
|
120
|
-
"""
|
|
121
|
-
Регистрирует инструменты API во внешних системах.
|
|
122
|
-
|
|
123
|
-
Args:
|
|
124
|
-
registry: Реестр команд
|
|
125
|
-
tool_names: Список имен инструментов API для регистрации
|
|
126
|
-
|
|
127
|
-
Returns:
|
|
128
|
-
Словарь с результатами регистрации инструментов
|
|
129
|
-
"""
|
|
130
|
-
results = {}
|
|
131
|
-
|
|
132
|
-
for tool_name in tool_names:
|
|
133
|
-
try:
|
|
134
|
-
# Генерируем схему инструмента
|
|
135
|
-
schema = cls.generate_tool_schema(tool_name, registry)
|
|
136
|
-
|
|
137
|
-
# Здесь будет код для регистрации инструмента во внешней системе
|
|
138
|
-
# Например, отправка схемы в API регистрации инструментов
|
|
139
|
-
|
|
140
|
-
results[tool_name] = {"status": "success", "schema": schema}
|
|
141
|
-
|
|
142
|
-
get_global_logger().info(f"Successfully registered tool: {tool_name}")
|
|
143
|
-
except Exception as e:
|
|
144
|
-
get_global_logger().debug(f"Error registering tool {tool_name}: {e}")
|
|
145
|
-
results[tool_name] = {"status": "error", "error": str(e)}
|
|
146
|
-
|
|
147
|
-
return results
|
|
148
83
|
|
|
149
84
|
@classmethod
|
|
150
85
|
def _extract_parameter_types(
|
|
@@ -194,55 +129,3 @@ class ToolIntegration:
|
|
|
194
129
|
return parameter_types
|
|
195
130
|
|
|
196
131
|
|
|
197
|
-
def generate_tool_help(tool_name: str, registry: CommandRegistry) -> str:
|
|
198
|
-
"""
|
|
199
|
-
Генерирует справочную информацию по инструменту API.
|
|
200
|
-
|
|
201
|
-
Args:
|
|
202
|
-
tool_name: Имя инструмента API
|
|
203
|
-
registry: Реестр команд
|
|
204
|
-
|
|
205
|
-
Returns:
|
|
206
|
-
Строка с описанием инструмента и доступных команд
|
|
207
|
-
"""
|
|
208
|
-
# Получаем метаданные всех команд
|
|
209
|
-
all_metadata = registry.get_all_metadata()
|
|
210
|
-
|
|
211
|
-
# Формируем текст справки
|
|
212
|
-
help_text = f"# Инструмент {tool_name}\n\n"
|
|
213
|
-
help_text += "Позволяет выполнять команды через JSON-RPC протокол.\n\n"
|
|
214
|
-
help_text += "## Доступные команды:\n\n"
|
|
215
|
-
|
|
216
|
-
# Добавляем информацию о каждой команде
|
|
217
|
-
for cmd_name, metadata in all_metadata.items():
|
|
218
|
-
help_text += f"### {cmd_name}\n"
|
|
219
|
-
help_text += f"{metadata['summary']}\n\n"
|
|
220
|
-
|
|
221
|
-
# Добавляем информацию о параметрах команды
|
|
222
|
-
if metadata["params"]:
|
|
223
|
-
help_text += "Параметры:\n"
|
|
224
|
-
for param_name, param_info in metadata["params"].items():
|
|
225
|
-
required = (
|
|
226
|
-
"обязательный"
|
|
227
|
-
if param_info.get("required", False)
|
|
228
|
-
else "опциональный"
|
|
229
|
-
)
|
|
230
|
-
help_text += f"- {param_name}: {required}\n"
|
|
231
|
-
help_text += "\n"
|
|
232
|
-
|
|
233
|
-
# Добавляем пример использования команды
|
|
234
|
-
if metadata.get("examples"):
|
|
235
|
-
example = metadata["examples"][0]
|
|
236
|
-
help_text += "Пример:\n"
|
|
237
|
-
help_text += "```json\n"
|
|
238
|
-
help_text += json.dumps(
|
|
239
|
-
{
|
|
240
|
-
"command": example.get("command", cmd_name),
|
|
241
|
-
"params": example.get("params", {}),
|
|
242
|
-
},
|
|
243
|
-
indent=2,
|
|
244
|
-
ensure_ascii=False,
|
|
245
|
-
)
|
|
246
|
-
help_text += "\n```\n\n"
|
|
247
|
-
|
|
248
|
-
return help_text
|