mcp-proxy-adapter 3.0.3__py3-none-any.whl → 3.1.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.
@@ -0,0 +1,152 @@
1
+ """
2
+ Example command with dependency injection.
3
+
4
+ This module demonstrates how to use dependency injection in commands.
5
+ """
6
+
7
+ from typing import Any, Dict, List, Optional
8
+
9
+ from mcp_proxy_adapter.commands import Command, SuccessResult
10
+ from mcp_proxy_adapter.commands.result import CommandResult
11
+
12
+
13
+ class EchoCommandResult(SuccessResult):
14
+ """
15
+ Result of echo command execution.
16
+ """
17
+
18
+ def __init__(self, message: str, timestamp: str, **kwargs):
19
+ """
20
+ Initializes echo command result.
21
+
22
+ Args:
23
+ message: Echoed message
24
+ timestamp: Time of execution
25
+ **kwargs: Additional parameters
26
+ """
27
+ data = {"message": message, "timestamp": timestamp}
28
+ data.update(kwargs)
29
+ super().__init__(data=data, message=f"Echo response: {message}")
30
+
31
+ @classmethod
32
+ def get_schema(cls) -> Dict[str, Any]:
33
+ """
34
+ Returns JSON schema for result validation.
35
+
36
+ Returns:
37
+ Dictionary with JSON schema
38
+ """
39
+ return {
40
+ "type": "object",
41
+ "properties": {
42
+ "success": {"type": "boolean"},
43
+ "data": {
44
+ "type": "object",
45
+ "properties": {
46
+ "message": {"type": "string"},
47
+ "timestamp": {"type": "string", "format": "date-time"}
48
+ },
49
+ "required": ["message", "timestamp"]
50
+ },
51
+ "message": {"type": "string"}
52
+ },
53
+ "required": ["success", "data"]
54
+ }
55
+
56
+
57
+ class TimeService:
58
+ """
59
+ Service for time-related operations.
60
+
61
+ This is a dependency that will be injected into the EchoCommand.
62
+ """
63
+
64
+ def get_current_time(self) -> str:
65
+ """
66
+ Get current time formatted as ISO string.
67
+
68
+ Returns:
69
+ Current time as ISO formatted string
70
+ """
71
+ from datetime import datetime
72
+ return datetime.now().isoformat()
73
+
74
+
75
+ class EchoCommand(Command):
76
+ """
77
+ Command that echoes back a message with timestamp.
78
+
79
+ This command demonstrates how to use dependency injection in commands
80
+ by accepting a service dependency in the constructor.
81
+ """
82
+
83
+ # Command name for JSON-RPC endpoint
84
+ name = "echo_di"
85
+ # Command result class
86
+ result_class = EchoCommandResult
87
+
88
+ def __init__(self, time_service: TimeService):
89
+ """
90
+ Initialize command with dependencies.
91
+
92
+ Args:
93
+ time_service: Service for getting the current time
94
+ """
95
+ self.time_service = time_service
96
+
97
+ async def execute(self, message: str = "Hello, World!") -> CommandResult:
98
+ """
99
+ Executes echo command.
100
+
101
+ Args:
102
+ message: Message to echo back
103
+
104
+ Returns:
105
+ Command execution result with message and timestamp
106
+ """
107
+ timestamp = self.time_service.get_current_time()
108
+ return EchoCommandResult(message=message, timestamp=timestamp)
109
+
110
+ @classmethod
111
+ def get_schema(cls) -> Dict[str, Any]:
112
+ """
113
+ Returns JSON schema for command parameters validation.
114
+
115
+ Returns:
116
+ Dictionary with JSON schema
117
+ """
118
+ return {
119
+ "type": "object",
120
+ "properties": {
121
+ "message": {
122
+ "type": "string",
123
+ "description": "Message to echo back"
124
+ }
125
+ },
126
+ "additionalProperties": False
127
+ }
128
+
129
+
130
+ # Example of registering the command with dependency injection
131
+ def register_echo_command():
132
+ """
133
+ Register echo command with dependencies.
134
+
135
+ This function shows how to:
136
+ 1. Create a service dependency
137
+ 2. Create a command instance with the dependency
138
+ 3. Register the command instance in the registry
139
+ """
140
+ from mcp_proxy_adapter.commands import registry, container
141
+
142
+ # Create and register service in the container
143
+ time_service = TimeService()
144
+ container.register("time_service", time_service)
145
+
146
+ # Create command with dependencies
147
+ echo_command = EchoCommand(time_service)
148
+
149
+ # Register command instance
150
+ registry.register(echo_command)
151
+
152
+ return echo_command
@@ -0,0 +1,8 @@
1
+ # pytest cache directory #
2
+
3
+ This directory contains data from the pytest's cache plugin,
4
+ which provides the `--lf` and `--ff` options, as well as the `cache` fixture.
5
+
6
+ **Do not** commit this to version control.
7
+
8
+ See [the docs](https://docs.pytest.org/en/stable/how-to/cache.html) for more information.
@@ -0,0 +1,249 @@
1
+ """
2
+ Example of using dependency injection in a microservice.
3
+
4
+ This example demonstrates how to:
5
+ 1. Create service dependencies
6
+ 2. Configure a dependency container
7
+ 3. Register commands with dependencies
8
+ 4. Run a microservice with dependency injection
9
+ """
10
+
11
+ import asyncio
12
+ import logging
13
+ import os
14
+ from typing import Dict, List, Any
15
+
16
+ import uvicorn
17
+ from fastapi import FastAPI
18
+
19
+ from mcp_proxy_adapter import create_app
20
+ from mcp_proxy_adapter.commands import registry, container
21
+ from examples.commands.echo_command_di import EchoCommand, TimeService
22
+
23
+
24
+ class DatabaseService:
25
+ """
26
+ Mock database service as an example dependency.
27
+ """
28
+
29
+ def __init__(self, connection_string: str):
30
+ """
31
+ Initialize database service.
32
+
33
+ Args:
34
+ connection_string: Database connection string
35
+ """
36
+ self.connection_string = connection_string
37
+ self.is_connected = False
38
+ self.data = {}
39
+
40
+ async def connect(self):
41
+ """Connect to the database."""
42
+ # Simulate connection delay
43
+ await asyncio.sleep(0.1)
44
+ self.is_connected = True
45
+ print(f"Connected to database: {self.connection_string}")
46
+
47
+ async def disconnect(self):
48
+ """Disconnect from the database."""
49
+ if self.is_connected:
50
+ # Simulate disconnection delay
51
+ await asyncio.sleep(0.1)
52
+ self.is_connected = False
53
+ print("Disconnected from database")
54
+
55
+ def get_item(self, key: str) -> Any:
56
+ """Get item from the database."""
57
+ if not self.is_connected:
58
+ raise RuntimeError("Not connected to database")
59
+ return self.data.get(key)
60
+
61
+ def set_item(self, key: str, value: Any):
62
+ """Set item in the database."""
63
+ if not self.is_connected:
64
+ raise RuntimeError("Not connected to database")
65
+ self.data[key] = value
66
+
67
+
68
+ class ConfigService:
69
+ """
70
+ Configuration service as an example dependency.
71
+ """
72
+
73
+ def __init__(self, config_path: str = None):
74
+ """
75
+ Initialize configuration service.
76
+
77
+ Args:
78
+ config_path: Path to config file (optional)
79
+ """
80
+ self.config_path = config_path
81
+ self.config = {
82
+ "app_name": "DI Example",
83
+ "version": "1.0.0",
84
+ "debug": True
85
+ }
86
+
87
+ def get(self, key: str, default: Any = None) -> Any:
88
+ """Get configuration value."""
89
+ return self.config.get(key, default)
90
+
91
+ def set(self, key: str, value: Any):
92
+ """Set configuration value."""
93
+ self.config[key] = value
94
+
95
+
96
+ class DataCommand(EchoCommand):
97
+ """
98
+ Command that uses multiple dependencies.
99
+
100
+ This command demonstrates using multiple injected dependencies.
101
+ """
102
+
103
+ name = "data"
104
+
105
+ def __init__(self, time_service: TimeService, db_service: DatabaseService, config: ConfigService):
106
+ """
107
+ Initialize command with multiple dependencies.
108
+
109
+ Args:
110
+ time_service: Service for time operations
111
+ db_service: Service for database operations
112
+ config: Service for configuration
113
+ """
114
+ super().__init__(time_service)
115
+ self.db_service = db_service
116
+ self.config = config
117
+
118
+ async def execute(self, action: str = "get", key: str = "data", value: str = None) -> Any:
119
+ """
120
+ Execute data command.
121
+
122
+ Args:
123
+ action: Operation to perform (get or set)
124
+ key: Data key
125
+ value: Data value (for set operation)
126
+
127
+ Returns:
128
+ Command result
129
+ """
130
+ timestamp = self.time_service.get_current_time()
131
+
132
+ if action == "get":
133
+ result = self.db_service.get_item(key)
134
+ return EchoCommand.result_class(
135
+ message=f"Got {key}: {result}",
136
+ timestamp=timestamp,
137
+ data=result
138
+ )
139
+ elif action == "set" and value is not None:
140
+ self.db_service.set_item(key, value)
141
+ return EchoCommand.result_class(
142
+ message=f"Set {key} to {value}",
143
+ timestamp=timestamp,
144
+ data={key: value}
145
+ )
146
+ else:
147
+ return EchoCommand.result_class(
148
+ message=f"Invalid action: {action}",
149
+ timestamp=timestamp,
150
+ error="invalid_action"
151
+ )
152
+
153
+ @classmethod
154
+ def get_schema(cls) -> Dict[str, Any]:
155
+ """Returns JSON schema for command parameters."""
156
+ return {
157
+ "type": "object",
158
+ "properties": {
159
+ "action": {
160
+ "type": "string",
161
+ "enum": ["get", "set"],
162
+ "description": "Action to perform on data"
163
+ },
164
+ "key": {
165
+ "type": "string",
166
+ "description": "Data key"
167
+ },
168
+ "value": {
169
+ "type": ["string", "null"],
170
+ "description": "Data value (for set action)"
171
+ }
172
+ },
173
+ "required": ["action", "key"]
174
+ }
175
+
176
+
177
+ async def setup_services():
178
+ """Set up and register all services in the container."""
179
+ # Create services
180
+ time_service = TimeService()
181
+ db_service = DatabaseService("sqlite://:memory:")
182
+ config_service = ConfigService()
183
+
184
+ # Connect to database
185
+ await db_service.connect()
186
+
187
+ # Register in container
188
+ container.register("time_service", time_service)
189
+ container.register("db_service", db_service)
190
+ container.register("config", config_service)
191
+
192
+ return {
193
+ "time_service": time_service,
194
+ "db_service": db_service,
195
+ "config": config_service
196
+ }
197
+
198
+
199
+ def register_commands(services):
200
+ """Register commands with dependencies."""
201
+ # Create command instances with dependencies
202
+ echo_command = EchoCommand(services["time_service"])
203
+ data_command = DataCommand(
204
+ services["time_service"],
205
+ services["db_service"],
206
+ services["config"]
207
+ )
208
+
209
+ # Register commands
210
+ registry.register(echo_command)
211
+ registry.register(data_command)
212
+
213
+
214
+ async def main():
215
+ """Run the example server with dependency injection."""
216
+ # Configure logging
217
+ logging.basicConfig(
218
+ level=logging.INFO,
219
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
220
+ )
221
+
222
+ # Setup services
223
+ services = await setup_services()
224
+
225
+ # Register commands with their dependencies
226
+ register_commands(services)
227
+
228
+ # Create FastAPI app with MCP adapter
229
+ app = create_app()
230
+
231
+ # Add startup and shutdown events
232
+ @app.on_event("shutdown")
233
+ async def shutdown_event():
234
+ # Disconnect database on shutdown
235
+ await services["db_service"].disconnect()
236
+
237
+ # Run the server
238
+ config = uvicorn.Config(
239
+ app=app,
240
+ host="0.0.0.0",
241
+ port=8000,
242
+ log_level="info"
243
+ )
244
+ server = uvicorn.Server(config)
245
+ await server.serve()
246
+
247
+
248
+ if __name__ == "__main__":
249
+ asyncio.run(main())
@@ -37,12 +37,15 @@ async def execute_command(command_name: str, params: Dict[str, Any], request_id:
37
37
  log = RequestLogger(__name__, request_id) if request_id else logger
38
38
 
39
39
  try:
40
- # Get command class from registry
41
- command_class = registry.get_command(command_name)
40
+ log.info(f"Executing command: {command_name}")
42
41
 
43
- # Execute command
42
+ # Get command class from registry and execute with parameters
44
43
  start_time = time.time()
44
+
45
+ # Use Command.run that handles instances with dependencies properly
46
+ command_class = registry.get_command(command_name)
45
47
  result = await command_class.run(**params)
48
+
46
49
  execution_time = time.time() - start_time
47
50
 
48
51
  log.info(f"Command '{command_name}' executed in {execution_time:.3f} sec")
@@ -1,10 +1,11 @@
1
1
  """
2
- Package with command implementation.
2
+ Commands module initialization file.
3
3
  """
4
4
 
5
5
  from mcp_proxy_adapter.commands.base import Command
6
- from mcp_proxy_adapter.commands.result import CommandResult, SuccessResult, ErrorResult
7
6
  from mcp_proxy_adapter.commands.command_registry import registry, CommandRegistry
7
+ from mcp_proxy_adapter.commands.dependency_container import container, DependencyContainer
8
+ from mcp_proxy_adapter.commands.result import CommandResult, SuccessResult, ErrorResult
8
9
 
9
10
  # Automatically discover and register commands
10
11
  registry.discover_commands()
@@ -12,8 +13,10 @@ registry.discover_commands()
12
13
  __all__ = [
13
14
  "Command",
14
15
  "CommandResult",
15
- "SuccessResult",
16
+ "SuccessResult",
16
17
  "ErrorResult",
17
18
  "registry",
18
- "CommandRegistry"
19
+ "CommandRegistry",
20
+ "container",
21
+ "DependencyContainer"
19
22
  ]
@@ -100,14 +100,29 @@ class Command(ABC):
100
100
  try:
101
101
  logger.debug(f"Running command {cls.__name__} with params: {kwargs}")
102
102
 
103
+ # Import registry here to avoid circular imports
104
+ from mcp_proxy_adapter.commands.command_registry import registry
105
+
106
+ # Get command name
107
+ if not hasattr(cls, "name") or not cls.name:
108
+ command_name = cls.__name__.lower()
109
+ if command_name.endswith("command"):
110
+ command_name = command_name[:-7]
111
+ else:
112
+ command_name = cls.name
113
+
103
114
  # Parameters validation
104
115
  validated_params = cls.validate_params(kwargs)
105
116
 
106
- # Create command instance
107
- command = cls()
108
-
109
- # Execute command
110
- result = await command.execute(**validated_params)
117
+ # Check if we have a registered instance for this command
118
+ if registry.has_instance(command_name):
119
+ # Use existing instance with dependencies
120
+ command = registry.get_command_instance(command_name)
121
+ result = await command.execute(**validated_params)
122
+ else:
123
+ # Create new instance for commands without dependencies
124
+ command = cls()
125
+ result = await command.execute(**validated_params)
111
126
 
112
127
  logger.debug(f"Command {cls.__name__} executed successfully")
113
128
  return result
@@ -248,53 +263,81 @@ class Command(ABC):
248
263
  # Example with all required parameters
249
264
  required_params = {k: v for k, v in params.items() if v.get("required", False)}
250
265
  if required_params:
251
- example_params = {}
266
+ sample_params = {}
252
267
  for param_name, param_info in required_params.items():
253
- # Generate suitable example value based on parameter type
268
+ # Try to generate sample value based on type
254
269
  param_type = param_info.get("type", "")
255
- if "str" in param_type.lower():
256
- example_params[param_name] = f"example_{param_name}"
257
- elif "int" in param_type.lower():
258
- example_params[param_name] = 123
259
- elif "float" in param_type.lower():
260
- example_params[param_name] = 123.45
261
- elif "bool" in param_type.lower():
262
- example_params[param_name] = True
270
+
271
+ if "str" in param_type:
272
+ sample_params[param_name] = f"sample_{param_name}"
273
+ elif "int" in param_type:
274
+ sample_params[param_name] = 1
275
+ elif "float" in param_type:
276
+ sample_params[param_name] = 1.0
277
+ elif "bool" in param_type:
278
+ sample_params[param_name] = True
279
+ elif "list" in param_type or "List" in param_type:
280
+ sample_params[param_name] = []
281
+ elif "dict" in param_type or "Dict" in param_type:
282
+ sample_params[param_name] = {}
263
283
  else:
264
- example_params[param_name] = f"value_for_{param_name}"
284
+ sample_params[param_name] = "..."
265
285
 
266
286
  examples.append({
267
287
  "command": cls.name,
268
- "params": example_params,
288
+ "params": sample_params,
269
289
  "description": f"Call {cls.name} command with required parameters"
270
290
  })
271
-
272
- # Add example with all parameters, if there are optional ones
273
- optional_params = {k: v for k, v in params.items() if not v.get("required", False)}
274
- if optional_params and required_params:
275
- full_example_params = dict(example_params) if 'example_params' in locals() else {}
276
291
 
277
- for param_name, param_info in optional_params.items():
278
- # Get default value or generate suitable example
279
- if "default" in param_info:
280
- full_example_params[param_name] = param_info["default"]
281
- else:
282
- # Generate suitable example value based on parameter type
292
+ # Example with all parameters (including optional ones)
293
+ if len(params) > len(required_params):
294
+ all_params = {}
295
+ for param_name, param_info in params.items():
296
+ # For required parameters, use the same values as above
297
+ if param_info.get("required", False):
298
+ # Try to generate sample value based on type
283
299
  param_type = param_info.get("type", "")
284
- if "str" in param_type.lower():
285
- full_example_params[param_name] = f"optional_{param_name}"
286
- elif "int" in param_type.lower():
287
- full_example_params[param_name] = 456
288
- elif "float" in param_type.lower():
289
- full_example_params[param_name] = 45.67
290
- elif "bool" in param_type.lower():
291
- full_example_params[param_name] = False
300
+
301
+ if "str" in param_type:
302
+ all_params[param_name] = f"sample_{param_name}"
303
+ elif "int" in param_type:
304
+ all_params[param_name] = 1
305
+ elif "float" in param_type:
306
+ all_params[param_name] = 1.0
307
+ elif "bool" in param_type:
308
+ all_params[param_name] = True
309
+ elif "list" in param_type or "List" in param_type:
310
+ all_params[param_name] = []
311
+ elif "dict" in param_type or "Dict" in param_type:
312
+ all_params[param_name] = {}
313
+ else:
314
+ all_params[param_name] = "..."
315
+ # For optional parameters, use their default values or a sample value
316
+ else:
317
+ if "default" in param_info:
318
+ all_params[param_name] = param_info["default"]
292
319
  else:
293
- full_example_params[param_name] = f"optional_value_for_{param_name}"
320
+ # Generate based on type
321
+ param_type = param_info.get("type", "")
322
+
323
+ if "str" in param_type:
324
+ all_params[param_name] = f"optional_{param_name}"
325
+ elif "int" in param_type:
326
+ all_params[param_name] = 42
327
+ elif "float" in param_type:
328
+ all_params[param_name] = 3.14
329
+ elif "bool" in param_type:
330
+ all_params[param_name] = False
331
+ elif "list" in param_type or "List" in param_type:
332
+ all_params[param_name] = ["sample"]
333
+ elif "dict" in param_type or "Dict" in param_type:
334
+ all_params[param_name] = {"key": "value"}
335
+ else:
336
+ all_params[param_name] = "sample_value"
294
337
 
295
338
  examples.append({
296
339
  "command": cls.name,
297
- "params": full_example_params,
340
+ "params": all_params,
298
341
  "description": f"Call {cls.name} command with all parameters"
299
342
  })
300
343
 
@@ -6,7 +6,7 @@ import importlib
6
6
  import inspect
7
7
  import os
8
8
  import pkgutil
9
- from typing import Any, Dict, List, Optional, Type, TypeVar, cast
9
+ from typing import Any, Dict, List, Optional, Type, TypeVar, Union, cast
10
10
 
11
11
  from mcp_proxy_adapter.commands.base import Command
12
12
  from mcp_proxy_adapter.core.errors import NotFoundError
@@ -25,17 +25,29 @@ class CommandRegistry:
25
25
  Initialize command registry.
26
26
  """
27
27
  self._commands: Dict[str, Type[Command]] = {}
28
+ self._instances: Dict[str, Command] = {}
28
29
 
29
- def register(self, command_class: Type[Command]) -> None:
30
+ def register(self, command: Union[Type[Command], Command]) -> None:
30
31
  """
31
- Registers command class in the registry.
32
+ Registers command class or instance in the registry.
32
33
 
33
34
  Args:
34
- command_class: Command class to register.
35
+ command: Command class or instance to register.
35
36
 
36
37
  Raises:
37
38
  ValueError: If command with the same name is already registered.
38
39
  """
40
+ # Determine if this is a class or an instance
41
+ if isinstance(command, type) and issubclass(command, Command):
42
+ command_class = command
43
+ command_instance = None
44
+ elif isinstance(command, Command):
45
+ command_class = command.__class__
46
+ command_instance = command
47
+ else:
48
+ raise ValueError(f"Invalid command type: {type(command)}. Expected Command class or instance.")
49
+
50
+ # Get command name
39
51
  if not hasattr(command_class, "name") or not command_class.name:
40
52
  # Use class name if name attribute is not set
41
53
  command_name = command_class.__name__.lower()
@@ -50,6 +62,11 @@ class CommandRegistry:
50
62
 
51
63
  logger.debug(f"Registering command: {command_name}")
52
64
  self._commands[command_name] = command_class
65
+
66
+ # Store instance if provided
67
+ if command_instance:
68
+ logger.debug(f"Storing instance for command: {command_name}")
69
+ self._instances[command_name] = command_instance
53
70
 
54
71
  def unregister(self, command_name: str) -> None:
55
72
  """
@@ -66,6 +83,10 @@ class CommandRegistry:
66
83
 
67
84
  logger.debug(f"Unregistering command: {command_name}")
68
85
  del self._commands[command_name]
86
+
87
+ # Remove instance if exists
88
+ if command_name in self._instances:
89
+ del self._instances[command_name]
69
90
 
70
91
  def command_exists(self, command_name: str) -> bool:
71
92
  """
@@ -96,6 +117,47 @@ class CommandRegistry:
96
117
  raise NotFoundError(f"Command '{command_name}' not found")
97
118
 
98
119
  return self._commands[command_name]
120
+
121
+ def get_command_instance(self, command_name: str) -> Command:
122
+ """
123
+ Gets command instance by name. If instance doesn't exist, creates new one.
124
+
125
+ Args:
126
+ command_name: Command name
127
+
128
+ Returns:
129
+ Command instance
130
+
131
+ Raises:
132
+ NotFoundError: If command is not found
133
+ """
134
+ if command_name not in self._commands:
135
+ raise NotFoundError(f"Command '{command_name}' not found")
136
+
137
+ # Return existing instance if available
138
+ if command_name in self._instances:
139
+ return self._instances[command_name]
140
+
141
+ # Otherwise create new instance without dependencies
142
+ # (this will raise error if command requires dependencies)
143
+ try:
144
+ command_class = self._commands[command_name]
145
+ return command_class()
146
+ except Exception as e:
147
+ logger.error(f"Failed to create instance of '{command_name}': {e}")
148
+ raise ValueError(f"Command '{command_name}' requires dependencies but was registered as class. Register an instance instead.") from e
149
+
150
+ def has_instance(self, command_name: str) -> bool:
151
+ """
152
+ Checks if command instance exists in registry.
153
+
154
+ Args:
155
+ command_name: Command name
156
+
157
+ Returns:
158
+ True if command instance exists, False otherwise
159
+ """
160
+ return command_name in self._instances
99
161
 
100
162
  def get_all_commands(self) -> Dict[str, Type[Command]]:
101
163
  """
@@ -225,6 +287,7 @@ class CommandRegistry:
225
287
  """
226
288
  logger.debug("Clearing command registry")
227
289
  self._commands.clear()
290
+ self._instances.clear()
228
291
 
229
292
 
230
293
  # Global command registry instance
@@ -0,0 +1,110 @@
1
+ """
2
+ Module for dependency injection container implementation.
3
+
4
+ This module provides a container for registering and resolving dependencies
5
+ for command instances in the microservice.
6
+ """
7
+
8
+ from typing import Any, Dict, Optional, Type, TypeVar, cast
9
+
10
+ T = TypeVar('T')
11
+
12
+
13
+ class DependencyContainer:
14
+ """
15
+ Container for managing dependencies.
16
+
17
+ This class provides functionality to register, resolve, and manage
18
+ dependencies that can be injected into command instances.
19
+ """
20
+
21
+ def __init__(self):
22
+ """Initialize dependency container."""
23
+ self._dependencies: Dict[str, Any] = {}
24
+ self._factories: Dict[str, callable] = {}
25
+ self._singletons: Dict[str, Any] = {}
26
+
27
+ def register(self, name: str, instance: Any) -> None:
28
+ """
29
+ Register a dependency instance with a given name.
30
+
31
+ Args:
32
+ name: The name to register the dependency under
33
+ instance: The dependency instance
34
+ """
35
+ self._dependencies[name] = instance
36
+
37
+ def register_factory(self, name: str, factory: callable) -> None:
38
+ """
39
+ Register a factory function that will be called to create the dependency.
40
+
41
+ Args:
42
+ name: The name to register the factory under
43
+ factory: A callable that creates the dependency
44
+ """
45
+ self._factories[name] = factory
46
+
47
+ def register_singleton(self, name: str, factory: callable) -> None:
48
+ """
49
+ Register a singleton factory that will create the instance only once.
50
+
51
+ Args:
52
+ name: The name to register the singleton under
53
+ factory: A callable that creates the singleton instance
54
+ """
55
+ self._factories[name] = factory
56
+ # Mark as singleton but don't create until requested
57
+ self._singletons[name] = None
58
+
59
+ def get(self, name: str) -> Any:
60
+ """
61
+ Get a dependency by name.
62
+
63
+ Args:
64
+ name: The name of the dependency to get
65
+
66
+ Returns:
67
+ The dependency instance
68
+
69
+ Raises:
70
+ KeyError: If the dependency is not registered
71
+ """
72
+ # Check for direct instance
73
+ if name in self._dependencies:
74
+ return self._dependencies[name]
75
+
76
+ # Check for singleton
77
+ if name in self._singletons:
78
+ # Create singleton if doesn't exist
79
+ if self._singletons[name] is None:
80
+ self._singletons[name] = self._factories[name]()
81
+ return self._singletons[name]
82
+
83
+ # Check for factory
84
+ if name in self._factories:
85
+ return self._factories[name]()
86
+
87
+ raise KeyError(f"Dependency '{name}' not registered")
88
+
89
+ def clear(self) -> None:
90
+ """Clear all registered dependencies."""
91
+ self._dependencies.clear()
92
+ self._factories.clear()
93
+ self._singletons.clear()
94
+
95
+ def has(self, name: str) -> bool:
96
+ """
97
+ Check if a dependency is registered.
98
+
99
+ Args:
100
+ name: The name of the dependency
101
+
102
+ Returns:
103
+ True if the dependency is registered, False otherwise
104
+ """
105
+ return (name in self._dependencies or
106
+ name in self._factories)
107
+
108
+
109
+ # Global dependency container instance
110
+ container = DependencyContainer()
@@ -1,5 +1,7 @@
1
1
  """
2
2
  Tests for middleware components.
3
+
4
+ These tests verify that middleware components work as expected.
3
5
  """
4
6
 
5
7
  import pytest
@@ -19,39 +21,38 @@ from mcp_proxy_adapter.api.middleware.auth import AuthMiddleware
19
21
  from mcp_proxy_adapter.api.middleware.rate_limit import RateLimitMiddleware
20
22
  from mcp_proxy_adapter.api.middleware.error_handling import ErrorHandlingMiddleware
21
23
  from mcp_proxy_adapter.api.middleware.performance import PerformanceMiddleware
22
- from mcp_proxy_adapter.core.errors import MicroserviceError, CommandError, ValidationError
24
+ from mcp_proxy_adapter.core.errors import MicroserviceError, CommandError, ValidationError, InvalidRequestError
23
25
 
24
26
 
25
27
  # Helper functions
26
28
  @pytest.mark.asyncio
27
29
  async def test_endpoint(request):
28
- """Test endpoint that returns a simple JSON response."""
29
- return JSONResponse({"message": "test"})
30
+ """Test endpoint for middleware tests."""
31
+ return Response(content="Test response", media_type="text/plain")
30
32
 
31
33
  @pytest.mark.asyncio
32
34
  async def error_endpoint(request):
33
- """Test endpoint that raises an error."""
34
- # Используем новый формат JSON-RPC ошибки
35
+ """Test endpoint that raises CommandError."""
35
36
  raise CommandError("Test error")
36
37
 
37
38
  @pytest.mark.asyncio
38
39
  async def validation_error_endpoint(request):
39
- """Test endpoint that raises a validation error."""
40
- # Изменяем параметр details на data в соответствии с новой структурой
40
+ """Test endpoint that raises ValidationError."""
41
+ # Вместо создания pydantic-модели напрямую вызываем нашу ValidationError
41
42
  raise ValidationError("Validation error", data={"field": "error"})
42
43
 
43
44
  @pytest.mark.asyncio
44
45
  async def json_rpc_error_endpoint(request):
45
- """Test endpoint that emulates JSON-RPC error handling."""
46
- # Сразу возвращаем готовый JSON-RPC ответ с ошибкой
46
+ """Test endpoint that raises InvalidRequestError."""
47
+ # Возвращаем заранее сформированный JSON-RPC ответ с ошибкой
47
48
  return JSONResponse(
48
49
  status_code=400,
49
50
  content={
50
51
  "jsonrpc": "2.0",
51
52
  "error": {
52
- "code": -32000, # JSON-RPC код ошибки
53
- "message": "Test error",
54
- "data": {"test": "data"}
53
+ "code": -32000,
54
+ "message": "Invalid JSON-RPC request",
55
+ "data": {}
55
56
  },
56
57
  "id": 1
57
58
  }
@@ -59,21 +60,24 @@ async def json_rpc_error_endpoint(request):
59
60
 
60
61
  # Test applications
61
62
  def create_test_app():
62
- """Create a test application with routes for testing."""
63
- routes = [
64
- Route("/test", test_endpoint),
65
- Route("/error", error_endpoint),
66
- Route("/validation", validation_error_endpoint),
67
- Route("/api/jsonrpc", json_rpc_error_endpoint),
68
- ]
69
- return Starlette(routes=routes)
63
+ """Create a test app with test endpoints."""
64
+ app = FastAPI()
65
+ app.add_route("/test", test_endpoint)
66
+ app.add_route("/error", error_endpoint)
67
+ app.add_route("/validation_error", validation_error_endpoint)
68
+ app.add_route("/json_rpc_error", json_rpc_error_endpoint)
69
+ # Добавим маршрут, имитирующий документацию
70
+ @app.get("/docs")
71
+ async def docs():
72
+ return Response(content="API Documentation", media_type="text/plain")
73
+ return app
70
74
 
71
75
 
72
76
  # Tests for BaseMiddleware
73
77
  def test_base_middleware():
74
78
  """Test that base middleware works correctly."""
75
79
  # Create a middleware that overrides methods
76
- class TestMiddleware(BaseMiddleware):
80
+ class MockMiddleware(BaseMiddleware):
77
81
  async def before_request(self, request):
78
82
  request.state.before_called = True
79
83
 
@@ -83,7 +87,7 @@ def test_base_middleware():
83
87
 
84
88
  # Create app with middleware
85
89
  app = create_test_app()
86
- app.add_middleware(TestMiddleware)
90
+ app.add_middleware(MockMiddleware)
87
91
 
88
92
  # Test
89
93
  client = TestClient(app)
@@ -176,7 +180,7 @@ def test_auth_middleware_public_path():
176
180
  response = client.get("/docs") # Public path
177
181
 
178
182
  # Verify
179
- assert response.status_code == 404 # 404 because path doesn't exist, but auth should pass
183
+ assert response.status_code == 200 # Путь существует и должен быть доступен
180
184
 
181
185
 
182
186
  def test_auth_middleware_disabled():
@@ -270,7 +274,7 @@ def test_error_handling_middleware_validation_error():
270
274
 
271
275
  # Test
272
276
  client = TestClient(app)
273
- response = client.get("/validation")
277
+ response = client.get("/validation_error")
274
278
 
275
279
  # Verify
276
280
  assert response.status_code == 400
@@ -292,15 +296,15 @@ def test_error_handling_middleware_jsonrpc_error():
292
296
  client = TestClient(app)
293
297
 
294
298
  # Выполняем запрос к JSON-RPC эндпоинту
295
- response = client.get("/api/jsonrpc")
299
+ response = client.get("/json_rpc_error")
296
300
 
297
301
  # Verify
298
302
  assert response.status_code == 400
299
303
  assert response.json()["jsonrpc"] == "2.0"
300
304
  assert "error" in response.json()
301
305
  assert response.json()["error"]["code"] == -32000 # Обновленный код JSON-RPC
302
- assert response.json()["error"]["message"] == "Test error"
303
- assert response.json()["error"]["data"] == {"test": "data"} # data вместо details
306
+ assert response.json()["error"]["message"] == "Invalid JSON-RPC request"
307
+ assert response.json()["error"]["data"] == {} # data вместо details
304
308
  assert response.json()["id"] == 1
305
309
 
306
310
 
@@ -29,7 +29,7 @@ class MockResultClass(CommandResult):
29
29
  }
30
30
 
31
31
 
32
- class TestCommand(Command):
32
+ class MockCommand(Command):
33
33
  """Test command for testing."""
34
34
 
35
35
  name = "test_command"
@@ -83,13 +83,13 @@ def test_error_result():
83
83
  assert result2.details == {"field": "invalid"}
84
84
 
85
85
 
86
- class TestCommandClass:
86
+ class CommandClassTests:
87
87
  """Test command class."""
88
88
 
89
89
  @pytest.mark.asyncio
90
90
  async def test_execute(self):
91
91
  """Test execute method."""
92
- command = TestCommand()
92
+ command = MockCommand()
93
93
  result = await command.execute(value="test_value")
94
94
  assert isinstance(result, MockResultClass)
95
95
  assert result.value == "test_value"
@@ -97,27 +97,27 @@ class TestCommandClass:
97
97
  @pytest.mark.asyncio
98
98
  async def test_run(self):
99
99
  """Test run method (with validation)."""
100
- result = await TestCommand.run(value="test_value")
100
+ result = await MockCommand.run(value="test_value")
101
101
  assert isinstance(result, MockResultClass)
102
102
  assert result.value == "test_value"
103
103
 
104
104
  def test_get_schema(self):
105
105
  """Test get_schema method."""
106
- schema = TestCommand.get_schema()
106
+ schema = MockCommand.get_schema()
107
107
  assert schema["type"] == "object"
108
108
  assert "value" in schema["properties"]
109
109
  assert schema["additionalProperties"] is False
110
110
 
111
111
  def test_get_result_schema(self):
112
112
  """Test get_result_schema method."""
113
- schema = TestCommand.get_result_schema()
113
+ schema = MockCommand.get_result_schema()
114
114
  assert schema["type"] == "object"
115
115
  assert "value" in schema["properties"]
116
116
  assert "value" in schema["required"]
117
117
 
118
118
  def test_get_param_info(self):
119
119
  """Test get_param_info method."""
120
- params = TestCommand.get_param_info()
120
+ params = MockCommand.get_param_info()
121
121
  assert "value" in params
122
122
  assert params["value"]["required"] is False
123
123
  assert params["value"]["default"] == "default"
@@ -1,3 +1,3 @@
1
1
  """Version information for MCP Microservice."""
2
2
 
3
- __version__ = "3.0.3"
3
+ __version__ = "3.1.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-proxy-adapter
3
- Version: 3.0.3
3
+ Version: 3.1.0
4
4
  Summary: Reliable microservice with unified JSON-RPC endpoint
5
5
  Home-page: https://github.com/yourusername/mcp-proxy-adapter
6
6
  Author: MCP Team
@@ -20,6 +20,7 @@ examples/basic_example/docs/EN/README.md,sha256=grOp0Adr3h9m-XZLCgLO6haBdUOjc4_V
20
20
  examples/basic_example/docs/RU/README.md,sha256=8jBObIj_3-eDmFt35ufIRw6mm41FyUXJD7olHmKS2QM,6736
21
21
  examples/basic_example/tests/conftest.py,sha256=6e85QRE50P9PMV1RXXVgtBQxy49hVLbevZ6dHFBrxRU,5917
22
22
  examples/commands/echo_command.py,sha256=7RuUZfP0YlKl9gks02NiuiYEiiNPa_8lk9CAZmzijyo,1569
23
+ examples/commands/echo_command_di.py,sha256=RMumhS6uKFjbvyjlW8IG24HB8UdOEVk-8WLD3xLfCS8,4307
23
24
  examples/commands/echo_result.py,sha256=q5IUEeFoi4PunzOhYD0zju2ZwETdGl35yj4DVXZfX68,1635
24
25
  examples/commands/get_date_command.py,sha256=ePQzZCV-n3tLf4IYv6Gn75t0AXXplXtikCDmrIMD2Q4,2537
25
26
  examples/commands/new_uuid4_command.py,sha256=lJQiPuVHtKCnOEUQukji6dwe0VOkddJ7sZXZ7XUsY6Y,2240
@@ -34,6 +35,8 @@ examples/complete_example/commands/__init__.py,sha256=cZDiwz3nLaeSCwR4oK7xlGgVsX
34
35
  examples/complete_example/commands/system_command.py,sha256=CwcBRWMODG7Ynr9Syaildu3APuuUdJ_yvQOggAAidg8,10717
35
36
  examples/complete_example/configs/config.dev.yaml,sha256=ASIwU8xzB-kinAsLGghv8-LCER1SUC1ed6K8BktOYPQ,1282
36
37
  examples/complete_example/configs/config.docker.yaml,sha256=DZWxauz5fcaiVFaphvf7zpvLI3oXjUUiXdX_bGlcBds,1389
38
+ examples/di_example/server.py,sha256=SRcKa_50L6Es7nDYSyTY3zzKQjlCdkAMAEjbwRe8esQ,7144
39
+ examples/di_example/.pytest_cache/README.md,sha256=c_1vzN2ALEGaay2YPWwxc7fal1WKxLWJ7ewt_kQ9ua0,302
37
40
  examples/minimal_example/README.md,sha256=VFKjDgpNjFowYNLD3sHHGsc1CVhCm30BWPGBqpLFKAc,2569
38
41
  examples/minimal_example/__init__.py,sha256=EloOqdgVLp7YA2DJOWMw0ZxFbVUCH2LqvZVq5RSfSbg,165
39
42
  examples/minimal_example/config.json,sha256=FSL0q_dAyGHJXp37bLesUvvNgrCiFXkQ8_P7nuUhIcQ,250
@@ -47,10 +50,10 @@ mcp_proxy_adapter/config.py,sha256=MjgZAld6TiD0F5oCyEaJhYhfEXVZxc5G5ke2SLKCV9A,5
47
50
  mcp_proxy_adapter/custom_openapi.py,sha256=a8DidORiO2sxMnRHlgSD0C6_6lZi3K56-PzF2p_t4NE,6316
48
51
  mcp_proxy_adapter/openapi.py,sha256=jyl5EPXcFhzFKEEMXxHeqF1U-SsYvtdlaKGU2QrekpU,13889
49
52
  mcp_proxy_adapter/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
- mcp_proxy_adapter/version.py,sha256=n1SIxB1BUfUjywGxxK6RFEDs6vMuHouqenqqD9oFKfg,71
53
+ mcp_proxy_adapter/version.py,sha256=udZyygpuqFCo_l08d6MC6yACDv6doZddjSPjgE-ghpQ,71
51
54
  mcp_proxy_adapter/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
55
  mcp_proxy_adapter/api/app.py,sha256=q3yYsNEBFXYr2TOJFxfh6bbF53NkmmgfzSFOtVq7xdc,14353
53
- mcp_proxy_adapter/api/handlers.py,sha256=4oAkc-szU3tggkLehqvTW2P5c3HTLIiqXQMwCY0ua2Q,7043
56
+ mcp_proxy_adapter/api/handlers.py,sha256=lc_4eakQgQVlnGjnVkOY-mIMkfyLk4iRfwdrWTyvuvM,7194
54
57
  mcp_proxy_adapter/api/schemas.py,sha256=xOmiSwHaapY6myEFnLu7o-LWVPM7vwmLYZXFo2c6NfE,12381
55
58
  mcp_proxy_adapter/api/tool_integration.py,sha256=mQNFiCkd4plY_A3fkG6auaM8D_1XiC9Jxp4Zrm1ngYE,10161
56
59
  mcp_proxy_adapter/api/tools.py,sha256=nSDXD4rGlFV_NlB91Y0x3N8WeWvWZ76B32-s-VAFARw,8084
@@ -61,10 +64,11 @@ mcp_proxy_adapter/api/middleware/error_handling.py,sha256=ShguFRn9GBBprevKO7n77E
61
64
  mcp_proxy_adapter/api/middleware/logging.py,sha256=wGtw4BqKMLgn5zqYd84DnVPtO3evfx2X-TxOCyAmysM,3679
62
65
  mcp_proxy_adapter/api/middleware/performance.py,sha256=dHBxTF43LEGXMKHMH3A8ybKmwAWURd_zswqq_oC4xbw,2454
63
66
  mcp_proxy_adapter/api/middleware/rate_limit.py,sha256=DIv_-ZUVmL-jEo_A5BlfnasZf25zT84AiIJDUUnXkpM,5041
64
- mcp_proxy_adapter/commands/__init__.py,sha256=cbds-RqRNyF-BM1PMa_j6OfyTENXzh54Z1aI2b3XpCU,475
65
- mcp_proxy_adapter/commands/base.py,sha256=NF7Xvssc6T3rb0s-LcDzjxG4IisNR0911EQpc-LbDyk,10670
66
- mcp_proxy_adapter/commands/command_registry.py,sha256=fNPsD1fQGDCp6qMnG3uMa-bDkPTAEw-GgFfoW0Sfwd8,8015
67
+ mcp_proxy_adapter/commands/__init__.py,sha256=Zxm-hw0rQGdlRAfhNVWaLvgqIYc3w8DYQS9hLUbjuSE,609
68
+ mcp_proxy_adapter/commands/base.py,sha256=zjDD7OwQN4wBrsJs1Xq3H1tZg0kUMBiRy8lpItzrqyU,12743
69
+ mcp_proxy_adapter/commands/command_registry.py,sha256=x55k4guzOlm7fVRolJ_iSFPRx2Snq4yBaMQdzVmsK0A,10460
67
70
  mcp_proxy_adapter/commands/config_command.py,sha256=-Z6BGaEQTf859l56zZpHYBeZFeIVdpMYybDrd7LOPIg,3553
71
+ mcp_proxy_adapter/commands/dependency_container.py,sha256=Uz9OPRAUZN7tsVrMVgXgPQcsRD2N-e2Ixg9XarPOlnY,3410
68
72
  mcp_proxy_adapter/commands/health_command.py,sha256=_tzxHwB_8vo53VBC6HnBv5fSfZL1pEuwlbrCcy_K78c,4087
69
73
  mcp_proxy_adapter/commands/help_command.py,sha256=UguKpvnmyW6qXZZyzdcJSVqKzLYi-AvaXo_UKyLR8js,6836
70
74
  mcp_proxy_adapter/commands/result.py,sha256=2WjftiAuhlyzOKmPJlQHo_b08ZCzWoK7cquUHFLVE-E,5534
@@ -78,14 +82,14 @@ mcp_proxy_adapter/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
78
82
  mcp_proxy_adapter/tests/conftest.py,sha256=VBA2wLeYfp9XAGSq4c489PVBIqMGAJiQPrT0k3kyzXw,2999
79
83
  mcp_proxy_adapter/tests/test_api_endpoints.py,sha256=ePtWCf0szD1JeY9WdHAhcKnuOzoSCpUcqZ_2DVhlC-A,10280
80
84
  mcp_proxy_adapter/tests/test_api_handlers.py,sha256=LeHO0o6eCxan6mt_8ZkUwSbeY7qYfoYMJ-bT_sSgdxc,11011
81
- mcp_proxy_adapter/tests/test_base_command.py,sha256=Oa-W3Z_758hLobsUcWMdZU2YxTueLusrzzRY1ZwSRY4,3778
85
+ mcp_proxy_adapter/tests/test_base_command.py,sha256=nSIi_mfjux8TL--65pMBfyg91EiLjJhI2P_ASWqyW-U,3779
82
86
  mcp_proxy_adapter/tests/test_batch_requests.py,sha256=9-gvhPq48AcEwGlhwgn3DWNhpleLA0f4luZNYMrqlXY,4103
83
87
  mcp_proxy_adapter/tests/test_command_registry.py,sha256=o5HHxlQ-D2ML0ufJK-lXQv-qYWccvalOHGVvpc8QFTU,6285
84
88
  mcp_proxy_adapter/tests/test_config.py,sha256=i4YbFhB3WI1wWKCxkG6l-UpFv2LAbhh4hmGipmYG1d0,3928
85
89
  mcp_proxy_adapter/tests/test_utils.py,sha256=K8DqdWDAV-cXjjeykehHpG5phfq5ydu2HLJzaAL282Y,1653
86
90
  mcp_proxy_adapter/tests/api/__init__.py,sha256=QzjeBIhrRLqfUKYmxVSCbMOoni5MAXgyAx9HxxB6JRs,27
87
91
  mcp_proxy_adapter/tests/api/test_cmd_endpoint.py,sha256=RFg_N7Ut1l_pncUPMaqVg85XV4KTpVe03yI_VA0Z47o,3843
88
- mcp_proxy_adapter/tests/api/test_middleware.py,sha256=A_-rpW0Kifg4SgjV3NxjQhOhFUSbnUWJjVJAAQWjEeQ,12063
92
+ mcp_proxy_adapter/tests/api/test_middleware.py,sha256=3Rgc09VJ7JG6_06oIgAVq6u7qJsaiuhW0KpfDjxk8Ac,12285
89
93
  mcp_proxy_adapter/tests/commands/__init__.py,sha256=DZxhtyr__AleyXN1s8Ef887tK5nsoHKfW4QXyzLaP0E,36
90
94
  mcp_proxy_adapter/tests/commands/test_config_command.py,sha256=ud0Y57xUhFiyrUKDyA0eBZ8IOKiGDpioijtwY-detC4,6522
91
95
  mcp_proxy_adapter/tests/commands/test_echo_command.py,sha256=c2dmqfx3ZfvDedy5vuxArFv7iHsReepMmD2Lchg4l3E,3437
@@ -102,8 +106,8 @@ mcp_proxy_adapter/tests/stubs/echo_command.py,sha256=Y7SA4IB5Lo_ncn78SDm9iRZvJSK
102
106
  mcp_proxy_adapter/tests/unit/__init__.py,sha256=RS-5UoSCcLKtr2jrAaZw_NG9MquA6BZmxq-P6cTw9ok,53
103
107
  mcp_proxy_adapter/tests/unit/test_base_command.py,sha256=ldDXQYk2eijbTgZioSBAhHzSAa_SuBKYqCutCEzUYTE,3924
104
108
  mcp_proxy_adapter/tests/unit/test_config.py,sha256=SZ62LXFOv_fsV0fmSIBdHWvapEyexKrioFRQo0I4pkg,5900
105
- mcp_proxy_adapter-3.0.3.dist-info/licenses/LICENSE,sha256=OkApFEwdgMCt_mbvUI-eIwKMSTe38K3XnU2DT5ub-wI,1072
106
- mcp_proxy_adapter-3.0.3.dist-info/METADATA,sha256=kyCCy5J9i92A38ew7UbqT4Tsy0KAKwPkLu0v00CnfFI,7537
107
- mcp_proxy_adapter-3.0.3.dist-info/WHEEL,sha256=DnLRTWE75wApRYVsjgc6wsVswC54sMSJhAEd4xhDpBk,91
108
- mcp_proxy_adapter-3.0.3.dist-info/top_level.txt,sha256=kxq3OC7vBtsFdy9dDVse4cOl-SV_QlvcTeSkuw_jw3I,27
109
- mcp_proxy_adapter-3.0.3.dist-info/RECORD,,
109
+ mcp_proxy_adapter-3.1.0.dist-info/licenses/LICENSE,sha256=OkApFEwdgMCt_mbvUI-eIwKMSTe38K3XnU2DT5ub-wI,1072
110
+ mcp_proxy_adapter-3.1.0.dist-info/METADATA,sha256=igXpT1nZAiVx9DOZu1yG4-HKgpfZ2Ej-68zkt5xaDtI,7537
111
+ mcp_proxy_adapter-3.1.0.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
112
+ mcp_proxy_adapter-3.1.0.dist-info/top_level.txt,sha256=kxq3OC7vBtsFdy9dDVse4cOl-SV_QlvcTeSkuw_jw3I,27
113
+ mcp_proxy_adapter-3.1.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.4.0)
2
+ Generator: setuptools (80.3.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5