mcp-proxy-adapter 3.0.0__py3-none-any.whl → 3.0.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 (85) hide show
  1. examples/basic_example/README.md +123 -9
  2. examples/basic_example/config.json +4 -0
  3. examples/basic_example/docs/EN/README.md +46 -5
  4. examples/basic_example/docs/RU/README.md +46 -5
  5. examples/basic_example/server.py +127 -21
  6. examples/complete_example/commands/system_command.py +1 -0
  7. examples/complete_example/server.py +68 -40
  8. examples/minimal_example/README.md +20 -6
  9. examples/minimal_example/config.json +7 -14
  10. examples/minimal_example/main.py +109 -40
  11. examples/minimal_example/simple_server.py +53 -14
  12. examples/minimal_example/tests/conftest.py +1 -1
  13. examples/minimal_example/tests/test_integration.py +8 -10
  14. examples/simple_server.py +12 -21
  15. examples/test_server.py +22 -14
  16. examples/tool_description_example.py +82 -0
  17. mcp_proxy_adapter/api/__init__.py +0 -0
  18. mcp_proxy_adapter/api/app.py +391 -0
  19. mcp_proxy_adapter/api/handlers.py +229 -0
  20. mcp_proxy_adapter/api/middleware/__init__.py +49 -0
  21. mcp_proxy_adapter/api/middleware/auth.py +146 -0
  22. mcp_proxy_adapter/api/middleware/base.py +79 -0
  23. mcp_proxy_adapter/api/middleware/error_handling.py +198 -0
  24. mcp_proxy_adapter/api/middleware/logging.py +96 -0
  25. mcp_proxy_adapter/api/middleware/performance.py +83 -0
  26. mcp_proxy_adapter/api/middleware/rate_limit.py +152 -0
  27. mcp_proxy_adapter/api/schemas.py +305 -0
  28. mcp_proxy_adapter/api/tool_integration.py +223 -0
  29. mcp_proxy_adapter/api/tools.py +198 -0
  30. mcp_proxy_adapter/commands/__init__.py +19 -0
  31. mcp_proxy_adapter/commands/base.py +301 -0
  32. mcp_proxy_adapter/commands/command_registry.py +231 -0
  33. mcp_proxy_adapter/commands/config_command.py +113 -0
  34. mcp_proxy_adapter/commands/health_command.py +136 -0
  35. mcp_proxy_adapter/commands/help_command.py +193 -0
  36. mcp_proxy_adapter/commands/result.py +215 -0
  37. mcp_proxy_adapter/config.py +9 -0
  38. mcp_proxy_adapter/core/__init__.py +0 -0
  39. mcp_proxy_adapter/core/errors.py +173 -0
  40. mcp_proxy_adapter/core/logging.py +205 -0
  41. mcp_proxy_adapter/core/utils.py +138 -0
  42. mcp_proxy_adapter/custom_openapi.py +47 -10
  43. mcp_proxy_adapter/py.typed +0 -0
  44. mcp_proxy_adapter/schemas/base_schema.json +114 -0
  45. mcp_proxy_adapter/schemas/openapi_schema.json +314 -0
  46. mcp_proxy_adapter/tests/__init__.py +0 -0
  47. mcp_proxy_adapter/tests/api/__init__.py +3 -0
  48. mcp_proxy_adapter/tests/api/test_cmd_endpoint.py +115 -0
  49. mcp_proxy_adapter/tests/api/test_middleware.py +336 -0
  50. mcp_proxy_adapter/tests/commands/__init__.py +3 -0
  51. mcp_proxy_adapter/tests/commands/test_config_command.py +211 -0
  52. mcp_proxy_adapter/tests/commands/test_echo_command.py +127 -0
  53. mcp_proxy_adapter/tests/commands/test_help_command.py +133 -0
  54. mcp_proxy_adapter/tests/conftest.py +131 -0
  55. mcp_proxy_adapter/tests/functional/__init__.py +3 -0
  56. mcp_proxy_adapter/tests/functional/test_api.py +253 -0
  57. mcp_proxy_adapter/tests/integration/__init__.py +3 -0
  58. mcp_proxy_adapter/tests/integration/test_cmd_integration.py +130 -0
  59. mcp_proxy_adapter/tests/integration/test_integration.py +255 -0
  60. mcp_proxy_adapter/tests/performance/__init__.py +3 -0
  61. mcp_proxy_adapter/tests/performance/test_performance.py +189 -0
  62. mcp_proxy_adapter/tests/stubs/__init__.py +10 -0
  63. mcp_proxy_adapter/tests/stubs/echo_command.py +104 -0
  64. mcp_proxy_adapter/tests/test_api_endpoints.py +271 -0
  65. mcp_proxy_adapter/tests/test_api_handlers.py +289 -0
  66. mcp_proxy_adapter/tests/test_base_command.py +123 -0
  67. mcp_proxy_adapter/tests/test_batch_requests.py +117 -0
  68. mcp_proxy_adapter/tests/test_command_registry.py +245 -0
  69. mcp_proxy_adapter/tests/test_config.py +127 -0
  70. mcp_proxy_adapter/tests/test_utils.py +65 -0
  71. mcp_proxy_adapter/tests/unit/__init__.py +3 -0
  72. mcp_proxy_adapter/tests/unit/test_base_command.py +130 -0
  73. mcp_proxy_adapter/tests/unit/test_config.py +217 -0
  74. mcp_proxy_adapter/version.py +1 -1
  75. {mcp_proxy_adapter-3.0.0.dist-info → mcp_proxy_adapter-3.0.2.dist-info}/METADATA +1 -1
  76. mcp_proxy_adapter-3.0.2.dist-info/RECORD +109 -0
  77. examples/basic_example/config.yaml +0 -20
  78. examples/basic_example/main.py +0 -50
  79. examples/complete_example/main.py +0 -67
  80. examples/minimal_example/config.yaml +0 -26
  81. mcp_proxy_adapter/framework.py +0 -109
  82. mcp_proxy_adapter-3.0.0.dist-info/RECORD +0 -58
  83. {mcp_proxy_adapter-3.0.0.dist-info → mcp_proxy_adapter-3.0.2.dist-info}/WHEEL +0 -0
  84. {mcp_proxy_adapter-3.0.0.dist-info → mcp_proxy_adapter-3.0.2.dist-info}/licenses/LICENSE +0 -0
  85. {mcp_proxy_adapter-3.0.0.dist-info → mcp_proxy_adapter-3.0.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,391 @@
1
+ """
2
+ Module for FastAPI application setup.
3
+ """
4
+
5
+ import json
6
+ from typing import Any, Dict, List, Optional, Union
7
+ from contextlib import asynccontextmanager
8
+
9
+ from fastapi import FastAPI, Body, Depends, HTTPException, Request
10
+ from fastapi.responses import JSONResponse, Response
11
+ from fastapi.middleware.cors import CORSMiddleware
12
+
13
+ from mcp_proxy_adapter.api.handlers import execute_command, handle_json_rpc, handle_batch_json_rpc, get_server_health, get_commands_list
14
+ from mcp_proxy_adapter.api.middleware import setup_middleware
15
+ from mcp_proxy_adapter.api.schemas import JsonRpcRequest, JsonRpcSuccessResponse, JsonRpcErrorResponse, HealthResponse, CommandListResponse, APIToolDescription
16
+ from mcp_proxy_adapter.api.tools import get_tool_description, execute_tool
17
+ from mcp_proxy_adapter.config import config
18
+ from mcp_proxy_adapter.core.errors import MicroserviceError, NotFoundError
19
+ from mcp_proxy_adapter.core.logging import logger, RequestLogger
20
+ from mcp_proxy_adapter.commands.command_registry import registry
21
+ from mcp_proxy_adapter.custom_openapi import custom_openapi
22
+
23
+
24
+ @asynccontextmanager
25
+ async def lifespan(app: FastAPI):
26
+ """
27
+ Lifespan manager for the FastAPI application. Handles startup and shutdown events.
28
+ """
29
+ # Startup events
30
+ from mcp_proxy_adapter.commands.command_registry import registry
31
+ from mcp_proxy_adapter.commands.help_command import HelpCommand
32
+
33
+ # Register built-in commands (help should be registered first)
34
+ if not registry.command_exists("help"):
35
+ registry.register(HelpCommand)
36
+
37
+ # Register other commands
38
+ registry.discover_commands()
39
+
40
+ logger.info(f"Application started with {len(registry.get_all_commands())} commands registered")
41
+
42
+ yield # Application is running
43
+
44
+ # Shutdown events
45
+ logger.info("Application shutting down")
46
+
47
+
48
+ def create_app() -> FastAPI:
49
+ """
50
+ Creates and configures FastAPI application.
51
+
52
+ Returns:
53
+ Configured FastAPI application.
54
+ """
55
+ # Create application
56
+ app = FastAPI(
57
+ title="MCP Proxy Adapter",
58
+ description="JSON-RPC API for interacting with MCP Proxy",
59
+ version="1.0.0",
60
+ docs_url="/docs",
61
+ redoc_url="/redoc",
62
+ lifespan=lifespan,
63
+ )
64
+
65
+ # Configure CORS
66
+ app.add_middleware(
67
+ CORSMiddleware,
68
+ allow_origins=["*"], # In production, specify concrete domains
69
+ allow_credentials=True,
70
+ allow_methods=["*"],
71
+ allow_headers=["*"],
72
+ )
73
+
74
+ # Setup middleware using the new middleware package
75
+ setup_middleware(app)
76
+
77
+ # Use custom OpenAPI schema
78
+ app.openapi = lambda: custom_openapi(app)
79
+
80
+ # Explicit endpoint for OpenAPI schema
81
+ @app.get("/openapi.json")
82
+ async def get_openapi_schema():
83
+ """
84
+ Returns optimized OpenAPI schema compatible with MCP-Proxy.
85
+ """
86
+ return custom_openapi(app)
87
+
88
+ # JSON-RPC handler
89
+ @app.post("/api/jsonrpc", response_model=Union[JsonRpcSuccessResponse, JsonRpcErrorResponse, List[Union[JsonRpcSuccessResponse, JsonRpcErrorResponse]]])
90
+ async def jsonrpc_endpoint(request: Request, request_data: Union[Dict[str, Any], List[Dict[str, Any]]] = Body(...)):
91
+ """
92
+ Endpoint for handling JSON-RPC requests.
93
+ Supports both single and batch requests.
94
+ """
95
+ # Get request_id from middleware state
96
+ request_id = getattr(request.state, "request_id", None)
97
+
98
+ # Create request logger for this endpoint
99
+ req_logger = RequestLogger(__name__, request_id) if request_id else logger
100
+
101
+ # Check if it's a batch request
102
+ if isinstance(request_data, list):
103
+ # Process batch request
104
+ if len(request_data) == 0:
105
+ # Empty batch request is invalid
106
+ req_logger.warning("Invalid Request: Empty batch request")
107
+ return JSONResponse(
108
+ status_code=400,
109
+ content={
110
+ "jsonrpc": "2.0",
111
+ "error": {
112
+ "code": -32600,
113
+ "message": "Invalid Request. Empty batch request"
114
+ },
115
+ "id": None
116
+ }
117
+ )
118
+ return await handle_batch_json_rpc(request_data, request)
119
+ else:
120
+ # Process single request
121
+ return await handle_json_rpc(request_data, request_id)
122
+
123
+ # Command execution endpoint (/cmd)
124
+ @app.post("/cmd")
125
+ async def cmd_endpoint(request: Request, command_data: Dict[str, Any] = Body(...)):
126
+ """
127
+ Universal endpoint for executing commands.
128
+ Supports two formats:
129
+ 1. CommandRequest:
130
+ {
131
+ "command": "command_name",
132
+ "params": {
133
+ // Command parameters
134
+ }
135
+ }
136
+
137
+ 2. JSON-RPC:
138
+ {
139
+ "jsonrpc": "2.0",
140
+ "method": "command_name",
141
+ "params": {
142
+ // Command parameters
143
+ },
144
+ "id": 123
145
+ }
146
+ """
147
+ # Get request_id from middleware state
148
+ request_id = getattr(request.state, "request_id", None)
149
+
150
+ # Create request logger for this endpoint
151
+ req_logger = RequestLogger(__name__, request_id) if request_id else logger
152
+
153
+ try:
154
+ # Determine request format (CommandRequest or JSON-RPC)
155
+ if "jsonrpc" in command_data and "method" in command_data:
156
+ # JSON-RPC format
157
+ return await handle_json_rpc(command_data, request_id)
158
+
159
+ # CommandRequest format
160
+ if "command" not in command_data:
161
+ req_logger.warning("Missing required field 'command'")
162
+ return JSONResponse(
163
+ status_code=200,
164
+ content={
165
+ "error": {
166
+ "code": -32600,
167
+ "message": "Отсутствует обязательное поле 'command'"
168
+ }
169
+ }
170
+ )
171
+
172
+ command_name = command_data["command"]
173
+ params = command_data.get("params", {})
174
+
175
+ req_logger.info(f"Executing command via /cmd: {command_name}, params: {params}")
176
+
177
+ # Check if command exists
178
+ if not registry.command_exists(command_name):
179
+ req_logger.warning(f"Command '{command_name}' not found")
180
+ return JSONResponse(
181
+ status_code=200,
182
+ content={
183
+ "error": {
184
+ "code": -32601,
185
+ "message": f"Команда '{command_name}' не найдена"
186
+ }
187
+ }
188
+ )
189
+
190
+ # Execute command
191
+ try:
192
+ result = await execute_command(command_name, params, request_id)
193
+ return {"result": result}
194
+ except MicroserviceError as e:
195
+ # Handle command execution errors
196
+ req_logger.error(f"Error executing command '{command_name}': {str(e)}")
197
+ return JSONResponse(
198
+ status_code=200,
199
+ content={
200
+ "error": e.to_dict()
201
+ }
202
+ )
203
+
204
+ except json.JSONDecodeError:
205
+ req_logger.error("JSON decode error")
206
+ return JSONResponse(
207
+ status_code=200,
208
+ content={
209
+ "error": {
210
+ "code": -32700,
211
+ "message": "Parse error"
212
+ }
213
+ }
214
+ )
215
+ except Exception as e:
216
+ req_logger.exception(f"Unexpected error: {str(e)}")
217
+ return JSONResponse(
218
+ status_code=200,
219
+ content={
220
+ "error": {
221
+ "code": -32603,
222
+ "message": "Internal error",
223
+ "data": {"details": str(e)}
224
+ }
225
+ }
226
+ )
227
+
228
+ # Direct command call
229
+ @app.post("/api/command/{command_name}")
230
+ async def command_endpoint(request: Request, command_name: str, params: Dict[str, Any] = Body(default={})):
231
+ """
232
+ Endpoint for direct command call.
233
+ """
234
+ # Get request_id from middleware state
235
+ request_id = getattr(request.state, "request_id", None)
236
+
237
+ try:
238
+ result = await execute_command(command_name, params, request_id)
239
+ return result
240
+ except MicroserviceError as e:
241
+ return JSONResponse(
242
+ status_code=e.code,
243
+ content=e.to_dict()
244
+ )
245
+
246
+ # Server health check
247
+ @app.get("/health", operation_id="health_check")
248
+ async def health_endpoint():
249
+ """
250
+ Проверить работоспособность сервиса.
251
+
252
+ Возвращает информацию о состоянии сервиса.
253
+ """
254
+ # Получаем базовую информацию о здоровье сервера
255
+ health_data = await get_server_health()
256
+
257
+ # Возвращаем информацию в формате, соответствующем схеме
258
+ return JSONResponse(content={
259
+ "status": "ok",
260
+ "model": "mcp-proxy-adapter",
261
+ "version": config.get("version", "1.0.0")
262
+ })
263
+
264
+ # List of available commands
265
+ @app.get("/api/commands", response_model=CommandListResponse)
266
+ async def commands_list_endpoint():
267
+ """
268
+ Endpoint for getting list of available commands.
269
+ """
270
+ commands = await get_commands_list()
271
+ return {"commands": commands}
272
+
273
+ # Get command information by name
274
+ @app.get("/api/commands/{command_name}")
275
+ async def command_info_endpoint(request: Request, command_name: str):
276
+ """
277
+ Endpoint for getting information about a specific command.
278
+ """
279
+ # Get request_id from middleware state
280
+ request_id = getattr(request.state, "request_id", None)
281
+
282
+ # Create request logger for this endpoint
283
+ req_logger = RequestLogger(__name__, request_id) if request_id else logger
284
+
285
+ try:
286
+ command_info = registry.get_command_info(command_name)
287
+ return command_info
288
+ except NotFoundError as e:
289
+ req_logger.warning(f"Command '{command_name}' not found")
290
+ return JSONResponse(
291
+ status_code=404,
292
+ content={
293
+ "error": {
294
+ "code": 404,
295
+ "message": f"Command '{command_name}' not found"
296
+ }
297
+ }
298
+ )
299
+
300
+ # Get API tool description
301
+ @app.get("/api/tools/{tool_name}")
302
+ async def tool_description_endpoint(tool_name: str, format: Optional[str] = "json"):
303
+ """
304
+ Получить подробное описание инструмента API.
305
+
306
+ Возвращает полное описание инструмента API с доступными командами,
307
+ их параметрами и примерами использования. Формат возвращаемых данных
308
+ может быть JSON или Markdown (text).
309
+
310
+ Args:
311
+ tool_name: Имя инструмента API
312
+ format: Формат вывода (json, text, markdown, html)
313
+ """
314
+ try:
315
+ description = get_tool_description(tool_name, format)
316
+
317
+ if format.lower() in ["text", "markdown", "html"]:
318
+ if format.lower() == "html":
319
+ return Response(content=description, media_type="text/html")
320
+ else:
321
+ return JSONResponse(
322
+ content={"description": description},
323
+ media_type="application/json"
324
+ )
325
+ else:
326
+ return description
327
+
328
+ except NotFoundError as e:
329
+ logger.warning(f"Tool not found: {tool_name}")
330
+ return JSONResponse(
331
+ status_code=404,
332
+ content={
333
+ "error": {
334
+ "code": 404,
335
+ "message": str(e)
336
+ }
337
+ }
338
+ )
339
+ except Exception as e:
340
+ logger.exception(f"Error generating tool description: {e}")
341
+ return JSONResponse(
342
+ status_code=500,
343
+ content={
344
+ "error": {
345
+ "code": 500,
346
+ "message": f"Error generating tool description: {str(e)}"
347
+ }
348
+ }
349
+ )
350
+
351
+ # Execute API tool
352
+ @app.post("/api/tools/{tool_name}")
353
+ async def execute_tool_endpoint(tool_name: str, params: Dict[str, Any] = Body(...)):
354
+ """
355
+ Выполнить инструмент API с указанными параметрами.
356
+
357
+ Args:
358
+ tool_name: Имя инструмента API
359
+ params: Параметры инструмента
360
+ """
361
+ try:
362
+ result = await execute_tool(tool_name, **params)
363
+ return result
364
+ except NotFoundError as e:
365
+ logger.warning(f"Tool not found: {tool_name}")
366
+ return JSONResponse(
367
+ status_code=404,
368
+ content={
369
+ "error": {
370
+ "code": 404,
371
+ "message": str(e)
372
+ }
373
+ }
374
+ )
375
+ except Exception as e:
376
+ logger.exception(f"Error executing tool {tool_name}: {e}")
377
+ return JSONResponse(
378
+ status_code=500,
379
+ content={
380
+ "error": {
381
+ "code": 500,
382
+ "message": f"Error executing tool: {str(e)}"
383
+ }
384
+ }
385
+ )
386
+
387
+ return app
388
+
389
+
390
+ # Create global application instance
391
+ app = create_app()
@@ -0,0 +1,229 @@
1
+ """
2
+ Module with API request handlers.
3
+ """
4
+
5
+ import json
6
+ import time
7
+ from typing import Any, Dict, List, Optional, Union
8
+
9
+ from fastapi import HTTPException, Request
10
+ from fastapi.responses import JSONResponse
11
+
12
+ from mcp_proxy_adapter.commands.command_registry import registry
13
+ from mcp_proxy_adapter.core.errors import (
14
+ MicroserviceError, NotFoundError, ParseError, InvalidRequestError,
15
+ MethodNotFoundError, InvalidParamsError, InternalError, CommandError
16
+ )
17
+ from mcp_proxy_adapter.core.logging import logger, RequestLogger, get_logger
18
+
19
+
20
+ async def execute_command(command_name: str, params: Dict[str, Any], request_id: Optional[str] = None) -> Dict[str, Any]:
21
+ """
22
+ Executes a command with the specified name and parameters.
23
+
24
+ Args:
25
+ command_name: Command name.
26
+ params: Command parameters.
27
+ request_id: Optional request identifier for logging context.
28
+
29
+ Returns:
30
+ Command execution result.
31
+
32
+ Raises:
33
+ MethodNotFoundError: If command is not found.
34
+ MicroserviceError: In case of command execution error.
35
+ """
36
+ # Create request logger if request_id is provided
37
+ log = RequestLogger(__name__, request_id) if request_id else logger
38
+
39
+ try:
40
+ # Get command class from registry
41
+ command_class = registry.get_command(command_name)
42
+
43
+ # Execute command
44
+ start_time = time.time()
45
+ result = await command_class.run(**params)
46
+ execution_time = time.time() - start_time
47
+
48
+ log.info(f"Command '{command_name}' executed in {execution_time:.3f} sec")
49
+
50
+ # Return result
51
+ return result.to_dict()
52
+ except NotFoundError as e:
53
+ log.error(f"Command not found: {command_name}")
54
+ # Преобразуем в MethodNotFoundError для соответствия JSON-RPC
55
+ raise MethodNotFoundError(f"Method not found: {command_name}")
56
+ except Exception as e:
57
+ log.exception(f"Error executing command '{command_name}': {e}")
58
+ if isinstance(e, MicroserviceError):
59
+ raise e
60
+ # Все остальные ошибки оборачиваем в InternalError
61
+ raise InternalError(f"Error executing command: {str(e)}", data={"original_error": str(e)})
62
+
63
+
64
+ async def handle_batch_json_rpc(batch_requests: List[Dict[str, Any]], request: Optional[Request] = None) -> List[Dict[str, Any]]:
65
+ """
66
+ Handles batch JSON-RPC requests.
67
+
68
+ Args:
69
+ batch_requests: List of JSON-RPC request data.
70
+ request: Original FastAPI request object.
71
+
72
+ Returns:
73
+ List of JSON-RPC responses.
74
+ """
75
+ responses = []
76
+
77
+ # Get request_id from request state if available
78
+ request_id = getattr(request.state, "request_id", None) if request else None
79
+
80
+ for request_data in batch_requests:
81
+ # Process each request in the batch
82
+ response = await handle_json_rpc(request_data, request_id)
83
+ responses.append(response)
84
+
85
+ return responses
86
+
87
+
88
+ async def handle_json_rpc(request_data: Dict[str, Any], request_id: Optional[str] = None) -> Dict[str, Any]:
89
+ """
90
+ Handles JSON-RPC request.
91
+
92
+ Args:
93
+ request_data: JSON-RPC request data.
94
+ request_id: Optional request identifier for logging context.
95
+
96
+ Returns:
97
+ JSON-RPC response.
98
+ """
99
+ # Create request logger if request_id is provided
100
+ log = RequestLogger(__name__, request_id) if request_id else logger
101
+
102
+ # Check JSON-RPC version
103
+ if request_data.get("jsonrpc") != "2.0":
104
+ return _create_error_response(
105
+ InvalidRequestError("Invalid Request. Expected jsonrpc: 2.0"),
106
+ request_data.get("id")
107
+ )
108
+
109
+ # Get method and parameters
110
+ method = request_data.get("method")
111
+ params = request_data.get("params", {})
112
+ json_rpc_id = request_data.get("id")
113
+
114
+ if not method:
115
+ return _create_error_response(
116
+ InvalidRequestError("Invalid Request. Method is required"),
117
+ json_rpc_id
118
+ )
119
+
120
+ log.info(f"Executing JSON-RPC method: {method}")
121
+
122
+ try:
123
+ # Execute command
124
+ result = await execute_command(method, params, request_id)
125
+
126
+ # Form successful response
127
+ return {
128
+ "jsonrpc": "2.0",
129
+ "result": result,
130
+ "id": json_rpc_id
131
+ }
132
+ except MicroserviceError as e:
133
+ # Method execution error
134
+ log.error(f"Method execution error: {str(e)}")
135
+ return _create_error_response(e, json_rpc_id)
136
+ except Exception as e:
137
+ # Internal server error
138
+ log.exception(f"Unhandled error in JSON-RPC handler: {e}")
139
+ return _create_error_response(
140
+ InternalError("Internal error", data={"error": str(e)}),
141
+ json_rpc_id
142
+ )
143
+
144
+
145
+ def _create_error_response(error: MicroserviceError, request_id: Any) -> Dict[str, Any]:
146
+ """
147
+ Creates JSON-RPC error response.
148
+
149
+ Args:
150
+ error: Error object.
151
+ request_id: Request ID from client.
152
+
153
+ Returns:
154
+ JSON-RPC error response dictionary.
155
+ """
156
+ return {
157
+ "jsonrpc": "2.0",
158
+ "error": error.to_dict(),
159
+ "id": request_id
160
+ }
161
+
162
+
163
+ async def get_server_health() -> Dict[str, Any]:
164
+ """
165
+ Gets server health information.
166
+
167
+ Returns:
168
+ Dictionary with server health information.
169
+ """
170
+ import os
171
+ import platform
172
+ import sys
173
+ import psutil
174
+ from datetime import datetime
175
+
176
+ # Get process start time
177
+ process = psutil.Process(os.getpid())
178
+ start_time = datetime.fromtimestamp(process.create_time())
179
+ uptime_seconds = (datetime.now() - start_time).total_seconds()
180
+
181
+ # Get system information
182
+ memory_info = process.memory_info()
183
+
184
+ return {
185
+ "status": "ok",
186
+ "version": "1.0.0", # Should be replaced with actual version
187
+ "uptime": uptime_seconds,
188
+ "components": {
189
+ "system": {
190
+ "python_version": sys.version,
191
+ "platform": platform.platform(),
192
+ "cpu_count": os.cpu_count()
193
+ },
194
+ "process": {
195
+ "pid": os.getpid(),
196
+ "memory_usage_mb": memory_info.rss / (1024 * 1024),
197
+ "start_time": start_time.isoformat()
198
+ },
199
+ "commands": {
200
+ "registered_count": len(registry.get_all_commands())
201
+ }
202
+ }
203
+ }
204
+
205
+
206
+ async def get_commands_list() -> Dict[str, Dict[str, Any]]:
207
+ """
208
+ Gets list of available commands.
209
+
210
+ Returns:
211
+ Dictionary with information about available commands.
212
+ """
213
+ result = {}
214
+
215
+ # Get all registered commands
216
+ all_commands = registry.get_all_commands()
217
+
218
+ for command_name, command_class in all_commands.items():
219
+ # Get schema information for the command
220
+ schema = command_class.get_schema()
221
+
222
+ # Add to result
223
+ result[command_name] = {
224
+ "name": command_name,
225
+ "schema": schema,
226
+ "description": schema.get("description", "")
227
+ }
228
+
229
+ return result
@@ -0,0 +1,49 @@
1
+ """
2
+ Middleware package for API.
3
+ This package contains middleware components for request processing.
4
+ """
5
+
6
+ from fastapi import FastAPI
7
+
8
+ from mcp_proxy_adapter.core.logging import logger
9
+ from .base import BaseMiddleware
10
+ from .logging import LoggingMiddleware
11
+ from .error_handling import ErrorHandlingMiddleware
12
+ from .auth import AuthMiddleware
13
+ from .rate_limit import RateLimitMiddleware
14
+ from .performance import PerformanceMiddleware
15
+
16
+ def setup_middleware(app: FastAPI) -> None:
17
+ """
18
+ Sets up middleware for application.
19
+
20
+ Args:
21
+ app: FastAPI application instance.
22
+ """
23
+ # Add error handling middleware first (last to execute)
24
+ app.add_middleware(ErrorHandlingMiddleware)
25
+
26
+ # Add logging middleware
27
+ app.add_middleware(LoggingMiddleware)
28
+
29
+ # Add rate limiting middleware if configured
30
+ from mcp_proxy_adapter.config import config
31
+ if config.get("rate_limit_enabled", False):
32
+ app.add_middleware(
33
+ RateLimitMiddleware,
34
+ rate_limit=config.get("rate_limit", 100),
35
+ time_window=config.get("rate_limit_window", 60)
36
+ )
37
+
38
+ # Добавляем authentication middleware с явным указанием auth_enabled
39
+ auth_enabled = config.get("auth_enabled", False)
40
+ app.add_middleware(
41
+ AuthMiddleware,
42
+ api_keys=config.get("api_keys", {}),
43
+ auth_enabled=auth_enabled
44
+ )
45
+
46
+ # Add performance middleware
47
+ app.add_middleware(PerformanceMiddleware)
48
+
49
+ logger.info(f"Middleware setup completed. Auth enabled: {auth_enabled}")