mcp-proxy-adapter 3.1.6__py3-none-any.whl → 4.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.
- mcp_proxy_adapter/api/app.py +65 -27
- mcp_proxy_adapter/api/handlers.py +1 -1
- mcp_proxy_adapter/api/middleware/error_handling.py +11 -10
- mcp_proxy_adapter/api/tool_integration.py +5 -2
- mcp_proxy_adapter/api/tools.py +3 -3
- mcp_proxy_adapter/commands/base.py +19 -1
- mcp_proxy_adapter/commands/command_registry.py +254 -8
- mcp_proxy_adapter/commands/hooks.py +260 -0
- mcp_proxy_adapter/commands/reload_command.py +211 -0
- mcp_proxy_adapter/commands/reload_settings_command.py +125 -0
- mcp_proxy_adapter/commands/settings_command.py +189 -0
- mcp_proxy_adapter/config.py +16 -1
- mcp_proxy_adapter/core/__init__.py +44 -0
- mcp_proxy_adapter/core/logging.py +87 -34
- mcp_proxy_adapter/core/settings.py +376 -0
- mcp_proxy_adapter/core/utils.py +2 -2
- mcp_proxy_adapter/custom_openapi.py +81 -2
- mcp_proxy_adapter/examples/README.md +124 -0
- mcp_proxy_adapter/examples/__init__.py +7 -0
- mcp_proxy_adapter/examples/basic_server/README.md +60 -0
- mcp_proxy_adapter/examples/basic_server/__init__.py +7 -0
- mcp_proxy_adapter/examples/basic_server/basic_custom_settings.json +39 -0
- mcp_proxy_adapter/examples/basic_server/config.json +35 -0
- mcp_proxy_adapter/examples/basic_server/custom_settings_example.py +238 -0
- mcp_proxy_adapter/examples/basic_server/server.py +98 -0
- mcp_proxy_adapter/examples/custom_commands/README.md +127 -0
- mcp_proxy_adapter/examples/custom_commands/__init__.py +27 -0
- mcp_proxy_adapter/examples/custom_commands/advanced_hooks.py +250 -0
- mcp_proxy_adapter/examples/custom_commands/auto_commands/__init__.py +6 -0
- mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_echo_command.py +103 -0
- mcp_proxy_adapter/examples/custom_commands/auto_commands/auto_info_command.py +111 -0
- mcp_proxy_adapter/examples/custom_commands/config.json +62 -0
- mcp_proxy_adapter/examples/custom_commands/custom_health_command.py +169 -0
- mcp_proxy_adapter/examples/custom_commands/custom_help_command.py +215 -0
- mcp_proxy_adapter/examples/custom_commands/custom_openapi_generator.py +76 -0
- mcp_proxy_adapter/examples/custom_commands/custom_settings.json +96 -0
- mcp_proxy_adapter/examples/custom_commands/custom_settings_manager.py +241 -0
- mcp_proxy_adapter/examples/custom_commands/data_transform_command.py +135 -0
- mcp_proxy_adapter/examples/custom_commands/echo_command.py +122 -0
- mcp_proxy_adapter/examples/custom_commands/hooks.py +230 -0
- mcp_proxy_adapter/examples/custom_commands/intercept_command.py +123 -0
- mcp_proxy_adapter/examples/custom_commands/manual_echo_command.py +103 -0
- mcp_proxy_adapter/examples/custom_commands/server.py +223 -0
- mcp_proxy_adapter/examples/custom_commands/test_hooks.py +176 -0
- mcp_proxy_adapter/examples/deployment/README.md +49 -0
- mcp_proxy_adapter/examples/deployment/__init__.py +7 -0
- mcp_proxy_adapter/examples/deployment/config.development.json +8 -0
- {examples/basic_example → mcp_proxy_adapter/examples/deployment}/config.json +11 -7
- mcp_proxy_adapter/examples/deployment/config.production.json +12 -0
- mcp_proxy_adapter/examples/deployment/config.staging.json +11 -0
- mcp_proxy_adapter/examples/deployment/docker-compose.yml +31 -0
- mcp_proxy_adapter/examples/deployment/run.sh +43 -0
- mcp_proxy_adapter/examples/deployment/run_docker.sh +84 -0
- mcp_proxy_adapter/openapi.py +3 -2
- mcp_proxy_adapter/tests/api/test_custom_openapi.py +617 -0
- mcp_proxy_adapter/tests/api/test_handlers.py +522 -0
- mcp_proxy_adapter/tests/api/test_schemas.py +546 -0
- mcp_proxy_adapter/tests/api/test_tool_integration.py +531 -0
- mcp_proxy_adapter/tests/unit/test_base_command.py +391 -85
- mcp_proxy_adapter/version.py +1 -1
- {mcp_proxy_adapter-3.1.6.dist-info → mcp_proxy_adapter-4.1.0.dist-info}/METADATA +3 -3
- mcp_proxy_adapter-4.1.0.dist-info/RECORD +110 -0
- {mcp_proxy_adapter-3.1.6.dist-info → mcp_proxy_adapter-4.1.0.dist-info}/WHEEL +1 -1
- {mcp_proxy_adapter-3.1.6.dist-info → mcp_proxy_adapter-4.1.0.dist-info}/top_level.txt +0 -1
- examples/__init__.py +0 -19
- examples/anti_patterns/README.md +0 -51
- examples/anti_patterns/__init__.py +0 -9
- examples/anti_patterns/bad_design/README.md +0 -72
- examples/anti_patterns/bad_design/global_state.py +0 -170
- examples/anti_patterns/bad_design/monolithic_command.py +0 -272
- examples/basic_example/README.md +0 -245
- examples/basic_example/__init__.py +0 -8
- examples/basic_example/commands/__init__.py +0 -5
- examples/basic_example/commands/echo_command.py +0 -95
- examples/basic_example/commands/math_command.py +0 -151
- examples/basic_example/commands/time_command.py +0 -152
- examples/basic_example/docs/EN/README.md +0 -177
- examples/basic_example/docs/RU/README.md +0 -177
- examples/basic_example/server.py +0 -151
- examples/basic_example/tests/conftest.py +0 -243
- examples/check_vstl_schema.py +0 -106
- examples/commands/echo_command.py +0 -52
- examples/commands/echo_command_di.py +0 -152
- examples/commands/echo_result.py +0 -65
- examples/commands/get_date_command.py +0 -98
- examples/commands/new_uuid4_command.py +0 -91
- examples/complete_example/Dockerfile +0 -24
- examples/complete_example/README.md +0 -92
- examples/complete_example/__init__.py +0 -8
- examples/complete_example/commands/__init__.py +0 -5
- examples/complete_example/commands/system_command.py +0 -328
- examples/complete_example/config.json +0 -41
- examples/complete_example/configs/config.dev.yaml +0 -40
- examples/complete_example/configs/config.docker.yaml +0 -40
- examples/complete_example/docker-compose.yml +0 -35
- examples/complete_example/requirements.txt +0 -20
- examples/complete_example/server.py +0 -113
- examples/di_example/.pytest_cache/README.md +0 -8
- examples/di_example/server.py +0 -249
- examples/fix_vstl_help.py +0 -123
- examples/minimal_example/README.md +0 -65
- examples/minimal_example/__init__.py +0 -8
- examples/minimal_example/config.json +0 -14
- examples/minimal_example/main.py +0 -136
- examples/minimal_example/simple_server.py +0 -163
- examples/minimal_example/tests/conftest.py +0 -171
- examples/minimal_example/tests/test_hello_command.py +0 -111
- examples/minimal_example/tests/test_integration.py +0 -181
- examples/patch_vstl_service.py +0 -105
- examples/patch_vstl_service_mcp.py +0 -108
- examples/server.py +0 -69
- examples/simple_server.py +0 -128
- examples/test_package_3.1.4.py +0 -177
- examples/test_server.py +0 -134
- examples/tool_description_example.py +0 -82
- mcp_proxy_adapter/py.typed +0 -0
- mcp_proxy_adapter-3.1.6.dist-info/RECORD +0 -118
- {mcp_proxy_adapter-3.1.6.dist-info → mcp_proxy_adapter-4.1.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,40 +0,0 @@
|
|
1
|
-
# Development configuration for complete MCP Microservice example
|
2
|
-
|
3
|
-
# Server settings
|
4
|
-
server:
|
5
|
-
host: "localhost" # Host to bind server
|
6
|
-
port: 8000 # Port to bind server
|
7
|
-
workers: 1 # Number of worker processes
|
8
|
-
debug: true # Enable debug mode
|
9
|
-
|
10
|
-
# Logging settings
|
11
|
-
logging:
|
12
|
-
level: "DEBUG" # Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
13
|
-
file: "logs/complete_example.log" # Log file path
|
14
|
-
rotation:
|
15
|
-
type: "size" # Log rotation type (size, time)
|
16
|
-
max_bytes: 10485760 # Maximum log file size (10 MB)
|
17
|
-
backup_count: 5 # Number of backup files
|
18
|
-
|
19
|
-
# Command discovery settings
|
20
|
-
discovery:
|
21
|
-
enabled: true
|
22
|
-
package: "commands" # Package to discover commands
|
23
|
-
|
24
|
-
# Cache settings
|
25
|
-
cache:
|
26
|
-
enabled: true
|
27
|
-
type: "file" # Cache type (file, memory, redis)
|
28
|
-
path: "cache" # Cache directory
|
29
|
-
ttl: 300 # Default TTL in seconds
|
30
|
-
|
31
|
-
# Database settings (for db_command)
|
32
|
-
database:
|
33
|
-
enabled: false # Database is disabled in development
|
34
|
-
type: "sqlite" # Database type
|
35
|
-
path: ":memory:" # Database path (in-memory SQLite)
|
36
|
-
|
37
|
-
# File operations settings (for file_command)
|
38
|
-
files:
|
39
|
-
base_path: "." # Base path for file operations
|
40
|
-
allowed_extensions: [".txt", ".log", ".json", ".yaml"] # Allowed file extensions
|
@@ -1,40 +0,0 @@
|
|
1
|
-
# Docker configuration for complete MCP Microservice example
|
2
|
-
|
3
|
-
# Server settings
|
4
|
-
server:
|
5
|
-
host: "0.0.0.0" # Bind to all interfaces in Docker
|
6
|
-
port: 8000 # Port to bind server
|
7
|
-
workers: 2 # Number of worker processes
|
8
|
-
debug: false # Disable debug mode in production
|
9
|
-
|
10
|
-
# Logging settings
|
11
|
-
logging:
|
12
|
-
level: "INFO" # Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
13
|
-
file: "/app/logs/complete_example.log" # Log file path in Docker container
|
14
|
-
rotation:
|
15
|
-
type: "size" # Log rotation type (size, time)
|
16
|
-
max_bytes: 10485760 # Maximum log file size (10 MB)
|
17
|
-
backup_count: 10 # Number of backup files
|
18
|
-
|
19
|
-
# Command discovery settings
|
20
|
-
discovery:
|
21
|
-
enabled: true
|
22
|
-
package: "commands" # Package to discover commands
|
23
|
-
|
24
|
-
# Cache settings
|
25
|
-
cache:
|
26
|
-
enabled: true
|
27
|
-
type: "file" # Cache type (file, memory, redis)
|
28
|
-
path: "/app/cache" # Cache directory in Docker container
|
29
|
-
ttl: 3600 # Default TTL in seconds (1 hour)
|
30
|
-
|
31
|
-
# Database settings (for db_command)
|
32
|
-
database:
|
33
|
-
enabled: true # Enable database in Docker environment
|
34
|
-
type: "sqlite" # Database type
|
35
|
-
path: "/app/data/db.sqlite" # Database path in Docker container
|
36
|
-
|
37
|
-
# File operations settings (for file_command)
|
38
|
-
files:
|
39
|
-
base_path: "/app/data" # Base path for file operations in Docker
|
40
|
-
allowed_extensions: [".txt", ".log", ".json", ".yaml"] # Allowed file extensions
|
@@ -1,35 +0,0 @@
|
|
1
|
-
version: '3.8'
|
2
|
-
|
3
|
-
services:
|
4
|
-
mcp-proxy-adapter:
|
5
|
-
build:
|
6
|
-
context: .
|
7
|
-
dockerfile: Dockerfile
|
8
|
-
image: mcp-proxy-adapter-example:latest
|
9
|
-
container_name: mcp-proxy-adapter-example
|
10
|
-
restart: unless-stopped
|
11
|
-
ports:
|
12
|
-
- "8000:8000"
|
13
|
-
volumes:
|
14
|
-
# Mount configuration, logs, and cache from host
|
15
|
-
- ./configs:/app/configs:ro
|
16
|
-
- ./logs:/app/logs
|
17
|
-
- ./cache:/app/cache
|
18
|
-
- ./data:/app/data
|
19
|
-
environment:
|
20
|
-
- PYTHONUNBUFFERED=1
|
21
|
-
- CONFIG_PATH=/app/configs/config.docker.yaml
|
22
|
-
networks:
|
23
|
-
- smart-assistant
|
24
|
-
user: "${UID:-1000}:${GID:-1000}" # Use host user ID to avoid permission issues
|
25
|
-
healthcheck:
|
26
|
-
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
27
|
-
interval: 30s
|
28
|
-
timeout: 10s
|
29
|
-
retries: 3
|
30
|
-
start_period: 10s
|
31
|
-
|
32
|
-
networks:
|
33
|
-
smart-assistant:
|
34
|
-
# Use external network if it exists, otherwise create it
|
35
|
-
external: true
|
@@ -1,20 +0,0 @@
|
|
1
|
-
# Python dependencies for MCP Microservice complete example
|
2
|
-
|
3
|
-
# Core dependencies
|
4
|
-
fastapi>=0.95.0,<1.0.0
|
5
|
-
pydantic>=2.0.0,<3.0.0
|
6
|
-
uvicorn>=0.22.0,<1.0.0
|
7
|
-
docstring-parser>=0.15,<1.0.0
|
8
|
-
typing-extensions>=4.5.0,<5.0.0
|
9
|
-
jsonrpc>=1.2.0,<2.0.0
|
10
|
-
|
11
|
-
# For system_command
|
12
|
-
psutil>=5.9.0,<6.0.0
|
13
|
-
pytz>=2023.3
|
14
|
-
|
15
|
-
# For file_command
|
16
|
-
aiofiles>=23.1.0,<24.0.0
|
17
|
-
|
18
|
-
# For db_command
|
19
|
-
aiosqlite>=0.17.0,<1.0.0
|
20
|
-
SQLAlchemy>=2.0.0,<3.0.0
|
@@ -1,113 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Complete MCP Microservice example.
|
3
|
-
|
4
|
-
This example demonstrates a complete microservice application with Docker support,
|
5
|
-
environment-specific configuration, and multiple commands.
|
6
|
-
"""
|
7
|
-
|
8
|
-
import os
|
9
|
-
import sys
|
10
|
-
import argparse
|
11
|
-
from pathlib import Path
|
12
|
-
import uvicorn
|
13
|
-
from mcp_proxy_adapter.api.app import create_app
|
14
|
-
from mcp_proxy_adapter.commands.command_registry import registry
|
15
|
-
from mcp_proxy_adapter.config import config
|
16
|
-
from mcp_proxy_adapter.core.logging import logger, setup_logging
|
17
|
-
|
18
|
-
# Add commands directory to path for local imports
|
19
|
-
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
20
|
-
|
21
|
-
|
22
|
-
def parse_args():
|
23
|
-
"""
|
24
|
-
Parse command line arguments.
|
25
|
-
|
26
|
-
Returns:
|
27
|
-
Parsed arguments
|
28
|
-
"""
|
29
|
-
parser = argparse.ArgumentParser(description="MCP Complete Example")
|
30
|
-
parser.add_argument(
|
31
|
-
"--config",
|
32
|
-
default="configs/config.dev.yaml",
|
33
|
-
help="Path to configuration file"
|
34
|
-
)
|
35
|
-
return parser.parse_args()
|
36
|
-
|
37
|
-
|
38
|
-
def ensure_directories():
|
39
|
-
"""
|
40
|
-
Create necessary directories based on configuration.
|
41
|
-
"""
|
42
|
-
base_dir = os.path.dirname(os.path.abspath(__file__))
|
43
|
-
os.makedirs(os.path.join(base_dir, "logs"), exist_ok=True)
|
44
|
-
os.makedirs(os.path.join(base_dir, "cache"), exist_ok=True)
|
45
|
-
os.makedirs(os.path.join(base_dir, "data"), exist_ok=True)
|
46
|
-
|
47
|
-
|
48
|
-
def setup_application(config_file=None):
|
49
|
-
"""
|
50
|
-
Setup and configure the microservice application.
|
51
|
-
|
52
|
-
Args:
|
53
|
-
config_file: Path to configuration file (optional)
|
54
|
-
|
55
|
-
Returns:
|
56
|
-
Configured FastAPI application
|
57
|
-
"""
|
58
|
-
if config_file is None:
|
59
|
-
args = parse_args()
|
60
|
-
config_file = args.config
|
61
|
-
current_dir = Path(__file__).parent.absolute()
|
62
|
-
config_path = current_dir / config_file
|
63
|
-
if not config_path.exists():
|
64
|
-
config_path = current_dir / "config.json"
|
65
|
-
ensure_directories()
|
66
|
-
if config_path.exists():
|
67
|
-
config.load_from_file(str(config_path))
|
68
|
-
logger.info(f"Loaded configuration from {config_path}")
|
69
|
-
else:
|
70
|
-
logger.warning(f"Configuration file {config_path} not found, using defaults")
|
71
|
-
app = create_app()
|
72
|
-
app.title = "Complete MCP Microservice Example"
|
73
|
-
app.description = "Full-featured microservice with Docker support"
|
74
|
-
app.version = "1.0.0"
|
75
|
-
# Discover and register commands from the commands directory
|
76
|
-
package_path = "commands"
|
77
|
-
try:
|
78
|
-
registered_commands = registry.get_all_commands()
|
79
|
-
for cmd_name in list(registered_commands.keys()):
|
80
|
-
try:
|
81
|
-
registry.unregister(cmd_name)
|
82
|
-
except Exception as e:
|
83
|
-
logger.debug(f"Error unregistering command {cmd_name}: {e}")
|
84
|
-
registry.discover_commands(package_path)
|
85
|
-
logger.info(f"Discovered commands from package: {package_path}")
|
86
|
-
except Exception as e:
|
87
|
-
logger.error(f"Error discovering commands: {e}")
|
88
|
-
return app
|
89
|
-
|
90
|
-
|
91
|
-
def main():
|
92
|
-
"""Run microservice with command discovery."""
|
93
|
-
log_level = config.get("logging.level", "INFO")
|
94
|
-
setup_logging(log_level)
|
95
|
-
app = setup_application()
|
96
|
-
host = config.get("server.host", "0.0.0.0")
|
97
|
-
port = config.get("server.port", 8000)
|
98
|
-
if "TEST_SERVER_PORT" in os.environ:
|
99
|
-
port = int(os.environ["TEST_SERVER_PORT"])
|
100
|
-
logger.info(f"Using test port from environment: {port}")
|
101
|
-
logger.info(f"Starting server on {host}:{port}")
|
102
|
-
debug = config.get("server.debug", False)
|
103
|
-
uvicorn.run(
|
104
|
-
app,
|
105
|
-
host=host,
|
106
|
-
port=port,
|
107
|
-
reload=debug,
|
108
|
-
log_level=log_level.lower()
|
109
|
-
)
|
110
|
-
|
111
|
-
|
112
|
-
if __name__ == "__main__":
|
113
|
-
main()
|
@@ -1,8 +0,0 @@
|
|
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.
|
examples/di_example/server.py
DELETED
@@ -1,249 +0,0 @@
|
|
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())
|
examples/fix_vstl_help.py
DELETED
@@ -1,123 +0,0 @@
|
|
1
|
-
#!/usr/bin/env python3
|
2
|
-
"""
|
3
|
-
Скрипт для исправления проблемы с обработкой JavaScript null в сервисе VSTL.
|
4
|
-
|
5
|
-
Этот скрипт демонстрирует проблему с обработкой null значений в VSTL
|
6
|
-
и способ её решения с помощью обновленной реализации метода validate_params.
|
7
|
-
"""
|
8
|
-
|
9
|
-
import sys
|
10
|
-
import json
|
11
|
-
import requests
|
12
|
-
from typing import Dict, Any, Optional
|
13
|
-
|
14
|
-
# URL и заголовки для VSTL сервиса
|
15
|
-
VSTL_URL = "http://localhost:8000/cmd"
|
16
|
-
HEADERS = {"Content-Type": "application/json"}
|
17
|
-
|
18
|
-
def call_vstl_help(params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
19
|
-
"""
|
20
|
-
Вызывает команду help в сервисе VSTL.
|
21
|
-
|
22
|
-
Args:
|
23
|
-
params: Параметры для команды help
|
24
|
-
|
25
|
-
Returns:
|
26
|
-
Dict[str, Any]: Ответ от сервиса
|
27
|
-
"""
|
28
|
-
payload = {
|
29
|
-
"jsonrpc": "2.0",
|
30
|
-
"method": "help",
|
31
|
-
"params": params or {},
|
32
|
-
"id": 1
|
33
|
-
}
|
34
|
-
|
35
|
-
response = requests.post(VSTL_URL, json=payload, headers=HEADERS)
|
36
|
-
return response.json()
|
37
|
-
|
38
|
-
def test_validate_params_fix():
|
39
|
-
"""
|
40
|
-
Демонстрирует проблему с обработкой null и решение с помощью
|
41
|
-
улучшенной реализации метода validate_params.
|
42
|
-
"""
|
43
|
-
# Оригинальная реализация метода validate_params
|
44
|
-
def original_validate_params(params: Dict[str, Any]) -> Dict[str, Any]:
|
45
|
-
if params is None:
|
46
|
-
params = {}
|
47
|
-
|
48
|
-
validated_params = params.copy()
|
49
|
-
|
50
|
-
for key, value in list(validated_params.items()):
|
51
|
-
if value is None or (isinstance(value, str) and value == ""):
|
52
|
-
if key in ["cmdname"]:
|
53
|
-
pass
|
54
|
-
else:
|
55
|
-
del validated_params[key]
|
56
|
-
|
57
|
-
return validated_params
|
58
|
-
|
59
|
-
# Улучшенная реализация метода validate_params
|
60
|
-
def improved_validate_params(params: Dict[str, Any]) -> Dict[str, Any]:
|
61
|
-
if params is None:
|
62
|
-
params = {}
|
63
|
-
|
64
|
-
validated_params = params.copy()
|
65
|
-
|
66
|
-
for key, value in list(validated_params.items()):
|
67
|
-
if value is None or (isinstance(value, str) and value.lower() in ["null", "none", ""]):
|
68
|
-
if key in ["cmdname"]:
|
69
|
-
validated_params[key] = None
|
70
|
-
else:
|
71
|
-
del validated_params[key]
|
72
|
-
|
73
|
-
return validated_params
|
74
|
-
|
75
|
-
# Тестирование оригинальной реализации
|
76
|
-
original_params = {"unknown_param": "null"}
|
77
|
-
|
78
|
-
print("\n=== Оригинальные параметры ===")
|
79
|
-
print(f"Параметры: {original_params}")
|
80
|
-
print("Результат validate_params (оригинальный):")
|
81
|
-
try:
|
82
|
-
print(original_validate_params(original_params))
|
83
|
-
except Exception as e:
|
84
|
-
print(f"ОШИБКА: {e}")
|
85
|
-
|
86
|
-
# Тестирование улучшенной реализации
|
87
|
-
print("\n=== Улучшенная обработка 'null' ===")
|
88
|
-
print(f"Параметры: {original_params}")
|
89
|
-
print("Результат validate_params (улучшенный):")
|
90
|
-
try:
|
91
|
-
print(improved_validate_params(original_params))
|
92
|
-
except Exception as e:
|
93
|
-
print(f"ОШИБКА: {e}")
|
94
|
-
|
95
|
-
# Проверка запроса к VSTL с null в параметрах
|
96
|
-
print("\n=== Тестирование запроса к VSTL ===")
|
97
|
-
|
98
|
-
# Тест с null в параметрах
|
99
|
-
print("\nТест 1: Запрос с null в параметрах")
|
100
|
-
response = call_vstl_help({"unknown_param": None})
|
101
|
-
print(f"Ответ: {json.dumps(response, indent=2)}")
|
102
|
-
|
103
|
-
# Тест с строковым "null" в параметрах
|
104
|
-
print("\nТест 2: Запрос со строковым 'null' в параметрах")
|
105
|
-
response = call_vstl_help({"unknown_param": "null"})
|
106
|
-
print(f"Ответ: {json.dumps(response, indent=2)}")
|
107
|
-
|
108
|
-
# Рекомендации по исправлению
|
109
|
-
print("\n=== Рекомендации по исправлению ===")
|
110
|
-
print("""
|
111
|
-
1. Обновите метод validate_params в файле commands/base.py на улучшенную версию:
|
112
|
-
- Добавьте проверку на строки "null" и "none" (в любом регистре)
|
113
|
-
- Для параметров, которые могут быть None, преобразуйте их в Python None
|
114
|
-
|
115
|
-
2. Обновите метод execute команды help, чтобы игнорировать неизвестные параметры:
|
116
|
-
- Добавьте аргумент **kwargs в сигнатуру метода
|
117
|
-
|
118
|
-
Эти изменения улучшат совместимость с клиентами, отправляющими JavaScript null
|
119
|
-
и сделают API более устойчивым к различным форматам входных данных.
|
120
|
-
""")
|
121
|
-
|
122
|
-
if __name__ == "__main__":
|
123
|
-
test_validate_params_fix()
|