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.
- examples/commands/echo_command_di.py +152 -0
- examples/di_example/.pytest_cache/README.md +8 -0
- examples/di_example/server.py +249 -0
- mcp_proxy_adapter/api/handlers.py +6 -3
- mcp_proxy_adapter/commands/__init__.py +7 -4
- mcp_proxy_adapter/commands/base.py +81 -38
- mcp_proxy_adapter/commands/command_registry.py +67 -4
- mcp_proxy_adapter/commands/dependency_container.py +110 -0
- mcp_proxy_adapter/tests/api/test_middleware.py +31 -27
- mcp_proxy_adapter/tests/test_base_command.py +7 -7
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-3.0.3.dist-info → mcp_proxy_adapter-3.1.0.dist-info}/METADATA +1 -1
- {mcp_proxy_adapter-3.0.3.dist-info → mcp_proxy_adapter-3.1.0.dist-info}/RECORD +16 -12
- {mcp_proxy_adapter-3.0.3.dist-info → mcp_proxy_adapter-3.1.0.dist-info}/WHEEL +1 -1
- {mcp_proxy_adapter-3.0.3.dist-info → mcp_proxy_adapter-3.1.0.dist-info}/licenses/LICENSE +0 -0
- {mcp_proxy_adapter-3.0.3.dist-info → mcp_proxy_adapter-3.1.0.dist-info}/top_level.txt +0 -0
@@ -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
|
-
|
41
|
-
command_class = registry.get_command(command_name)
|
40
|
+
log.info(f"Executing command: {command_name}")
|
42
41
|
|
43
|
-
#
|
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
|
-
|
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
|
-
#
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
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
|
-
|
266
|
+
sample_params = {}
|
252
267
|
for param_name, param_info in required_params.items():
|
253
|
-
#
|
268
|
+
# Try to generate sample value based on type
|
254
269
|
param_type = param_info.get("type", "")
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
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
|
-
|
284
|
+
sample_params[param_name] = "..."
|
265
285
|
|
266
286
|
examples.append({
|
267
287
|
"command": cls.name,
|
268
|
-
"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
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
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
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
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
|
-
|
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":
|
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,
|
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
|
-
|
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
|
29
|
-
return
|
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
|
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
|
40
|
-
#
|
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
|
46
|
-
#
|
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,
|
53
|
-
"message": "
|
54
|
-
"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
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
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(
|
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 ==
|
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("/
|
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("/
|
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"] == "
|
303
|
-
assert response.json()["error"]["data"] == {
|
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
|
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
|
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 =
|
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
|
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 =
|
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 =
|
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 =
|
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"
|
mcp_proxy_adapter/version.py
CHANGED
@@ -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=
|
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=
|
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=
|
65
|
-
mcp_proxy_adapter/commands/base.py,sha256=
|
66
|
-
mcp_proxy_adapter/commands/command_registry.py,sha256=
|
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=
|
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=
|
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.
|
106
|
-
mcp_proxy_adapter-3.0.
|
107
|
-
mcp_proxy_adapter-3.0.
|
108
|
-
mcp_proxy_adapter-3.0.
|
109
|
-
mcp_proxy_adapter-3.0.
|
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,,
|
File without changes
|
File without changes
|