mcp-proxy-adapter 2.1.17__py3-none-any.whl → 3.0.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/__init__.py +19 -0
- examples/anti_patterns/README.md +51 -0
- examples/anti_patterns/__init__.py +9 -0
- examples/anti_patterns/bad_design/README.md +72 -0
- examples/anti_patterns/bad_design/global_state.py +170 -0
- examples/anti_patterns/bad_design/monolithic_command.py +272 -0
- examples/basic_example/README.md +131 -0
- examples/basic_example/__init__.py +8 -0
- examples/basic_example/commands/__init__.py +5 -0
- examples/basic_example/commands/echo_command.py +95 -0
- examples/basic_example/commands/math_command.py +151 -0
- examples/basic_example/commands/time_command.py +152 -0
- examples/basic_example/config.json +21 -0
- examples/basic_example/config.yaml +20 -0
- examples/basic_example/docs/EN/README.md +136 -0
- examples/basic_example/docs/RU/README.md +136 -0
- examples/basic_example/main.py +50 -0
- examples/basic_example/server.py +45 -0
- examples/basic_example/tests/conftest.py +243 -0
- examples/commands/echo_command.py +52 -0
- examples/commands/echo_result.py +65 -0
- examples/commands/get_date_command.py +98 -0
- examples/commands/new_uuid4_command.py +91 -0
- examples/complete_example/Dockerfile +24 -0
- examples/complete_example/README.md +92 -0
- examples/complete_example/__init__.py +8 -0
- examples/complete_example/commands/__init__.py +5 -0
- examples/complete_example/commands/system_command.py +327 -0
- examples/complete_example/config.json +41 -0
- examples/complete_example/configs/config.dev.yaml +40 -0
- examples/complete_example/configs/config.docker.yaml +40 -0
- examples/complete_example/docker-compose.yml +35 -0
- examples/complete_example/main.py +67 -0
- examples/complete_example/requirements.txt +20 -0
- examples/complete_example/server.py +85 -0
- examples/minimal_example/README.md +51 -0
- examples/minimal_example/__init__.py +8 -0
- examples/minimal_example/config.json +21 -0
- examples/minimal_example/config.yaml +26 -0
- examples/minimal_example/main.py +67 -0
- examples/minimal_example/simple_server.py +124 -0
- examples/minimal_example/tests/conftest.py +171 -0
- examples/minimal_example/tests/test_hello_command.py +111 -0
- examples/minimal_example/tests/test_integration.py +183 -0
- examples/server.py +69 -0
- examples/simple_server.py +137 -0
- examples/test_server.py +126 -0
- mcp_proxy_adapter/__init__.py +33 -1
- mcp_proxy_adapter/config.py +186 -0
- mcp_proxy_adapter/custom_openapi.py +125 -0
- mcp_proxy_adapter/framework.py +109 -0
- mcp_proxy_adapter/openapi.py +403 -0
- mcp_proxy_adapter/version.py +3 -0
- mcp_proxy_adapter-3.0.0.dist-info/METADATA +200 -0
- mcp_proxy_adapter-3.0.0.dist-info/RECORD +58 -0
- {mcp_proxy_adapter-2.1.17.dist-info → mcp_proxy_adapter-3.0.0.dist-info}/top_level.txt +1 -0
- mcp_proxy_adapter/adapter.py +0 -697
- mcp_proxy_adapter/analyzers/__init__.py +0 -1
- mcp_proxy_adapter/analyzers/docstring_analyzer.py +0 -199
- mcp_proxy_adapter/analyzers/type_analyzer.py +0 -151
- mcp_proxy_adapter/dispatchers/__init__.py +0 -1
- mcp_proxy_adapter/dispatchers/base_dispatcher.py +0 -85
- mcp_proxy_adapter/dispatchers/json_rpc_dispatcher.py +0 -262
- mcp_proxy_adapter/examples/analyze_config.py +0 -141
- mcp_proxy_adapter/examples/basic_integration.py +0 -155
- mcp_proxy_adapter/examples/docstring_and_schema_example.py +0 -69
- mcp_proxy_adapter/examples/extension_example.py +0 -72
- mcp_proxy_adapter/examples/help_best_practices.py +0 -67
- mcp_proxy_adapter/examples/help_usage.py +0 -64
- mcp_proxy_adapter/examples/mcp_proxy_client.py +0 -131
- mcp_proxy_adapter/examples/openapi_server.py +0 -383
- mcp_proxy_adapter/examples/project_structure_example.py +0 -47
- mcp_proxy_adapter/examples/testing_example.py +0 -64
- mcp_proxy_adapter/models.py +0 -47
- mcp_proxy_adapter/registry.py +0 -439
- mcp_proxy_adapter/schema.py +0 -257
- mcp_proxy_adapter/testing_utils.py +0 -112
- mcp_proxy_adapter/validators/__init__.py +0 -1
- mcp_proxy_adapter/validators/docstring_validator.py +0 -75
- mcp_proxy_adapter/validators/metadata_validator.py +0 -76
- mcp_proxy_adapter-2.1.17.dist-info/METADATA +0 -376
- mcp_proxy_adapter-2.1.17.dist-info/RECORD +0 -30
- {mcp_proxy_adapter-2.1.17.dist-info → mcp_proxy_adapter-3.0.0.dist-info}/WHEEL +0 -0
- {mcp_proxy_adapter-2.1.17.dist-info → mcp_proxy_adapter-3.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,262 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Implementation of a JSON-RPC based command dispatcher.
|
3
|
-
|
4
|
-
CHANGELOG:
|
5
|
-
- 2024-06-13: execute() now always returns awaitable. If handler is sync and for any reason result is not awaitable, it is wrapped in an async function and awaited. This guarantees await-safety for all handler types and fixes 'object ... can't be used in await expression' errors in all environments.
|
6
|
-
"""
|
7
|
-
from typing import Dict, Any, Callable, List, Optional, Union
|
8
|
-
import inspect
|
9
|
-
import logging
|
10
|
-
import traceback
|
11
|
-
from .base_dispatcher import BaseDispatcher
|
12
|
-
import asyncio
|
13
|
-
|
14
|
-
logger = logging.getLogger("command_registry")
|
15
|
-
|
16
|
-
print('[DEBUG] LOADED json_rpc_dispatcher.py')
|
17
|
-
|
18
|
-
class CommandError(Exception):
|
19
|
-
"""Base class for command errors"""
|
20
|
-
pass
|
21
|
-
|
22
|
-
class CommandNotFoundError(CommandError):
|
23
|
-
"""Error raised when attempting to execute a non-existent command"""
|
24
|
-
pass
|
25
|
-
|
26
|
-
class CommandExecutionError(CommandError):
|
27
|
-
"""Error raised during command execution"""
|
28
|
-
pass
|
29
|
-
|
30
|
-
class JsonRpcDispatcher(BaseDispatcher):
|
31
|
-
"""
|
32
|
-
JSON-RPC based command dispatcher.
|
33
|
-
|
34
|
-
Implements the BaseDispatcher interface for handling commands in JSON-RPC 2.0 format.
|
35
|
-
Supports registration, execution, and retrieval of command information.
|
36
|
-
|
37
|
-
Best practice:
|
38
|
-
----------------
|
39
|
-
Register handlers explicitly using register_handler (no decorators!).
|
40
|
-
Both sync and async handlers are supported.
|
41
|
-
|
42
|
-
Example:
|
43
|
-
import asyncio
|
44
|
-
from mcp_proxy_adapter.dispatchers.json_rpc_dispatcher import JsonRpcDispatcher
|
45
|
-
|
46
|
-
def sync_handler(x):
|
47
|
-
return x + 1
|
48
|
-
|
49
|
-
async def async_handler(x):
|
50
|
-
await asyncio.sleep(0.1)
|
51
|
-
return x * 2
|
52
|
-
|
53
|
-
dispatcher = JsonRpcDispatcher()
|
54
|
-
dispatcher.register_handler('sync', sync_handler, description='Sync handler')
|
55
|
-
dispatcher.register_handler('async', async_handler, description='Async handler')
|
56
|
-
|
57
|
-
# Call sync handler
|
58
|
-
result_sync = asyncio.run(dispatcher.execute('sync', x=10))
|
59
|
-
print(result_sync) # 11
|
60
|
-
|
61
|
-
# Call async handler
|
62
|
-
result_async = asyncio.run(dispatcher.execute('async', x=10))
|
63
|
-
print(result_async) # 20
|
64
|
-
"""
|
65
|
-
|
66
|
-
def __init__(self):
|
67
|
-
"""Initializes a new dispatcher instance"""
|
68
|
-
self._handlers = {}
|
69
|
-
self._metadata = {}
|
70
|
-
|
71
|
-
# Register the built-in help command
|
72
|
-
self.register_handler(
|
73
|
-
command="help",
|
74
|
-
handler=self._help_command,
|
75
|
-
description=(
|
76
|
-
"Returns information about available commands.\n"
|
77
|
-
"Best practice: Register handlers explicitly using register_handler (no decorators).\n"
|
78
|
-
"Example: dispatcher.register_handler('mycmd', my_handler, description='...')"
|
79
|
-
),
|
80
|
-
summary="Command help",
|
81
|
-
params={
|
82
|
-
"cmdname": {
|
83
|
-
"type": "string",
|
84
|
-
"description": "Command name for detailed information",
|
85
|
-
"required": False
|
86
|
-
}
|
87
|
-
}
|
88
|
-
)
|
89
|
-
|
90
|
-
def register_handler(
|
91
|
-
self,
|
92
|
-
command: str,
|
93
|
-
handler: Callable,
|
94
|
-
description: str = "",
|
95
|
-
summary: str = "",
|
96
|
-
params: Dict[str, Any] = None
|
97
|
-
) -> None:
|
98
|
-
"""
|
99
|
-
Registers a command handler.
|
100
|
-
|
101
|
-
Args:
|
102
|
-
command: Command name
|
103
|
-
handler: Command handler function
|
104
|
-
description: Command description
|
105
|
-
summary: Brief command summary
|
106
|
-
params: Command parameters description
|
107
|
-
"""
|
108
|
-
if not params:
|
109
|
-
params = {}
|
110
|
-
|
111
|
-
# Save the handler
|
112
|
-
self._handlers[command] = handler
|
113
|
-
|
114
|
-
# Save metadata
|
115
|
-
self._metadata[command] = {
|
116
|
-
"description": description,
|
117
|
-
"summary": summary or command.replace("_", " ").title(),
|
118
|
-
"params": params
|
119
|
-
}
|
120
|
-
|
121
|
-
logger.debug(f"Registered command: {command}")
|
122
|
-
|
123
|
-
async def _call_handler_always_awaitable(self, handler, kwargs):
|
124
|
-
loop = asyncio.get_running_loop()
|
125
|
-
sig = inspect.signature(handler)
|
126
|
-
params = sig.parameters
|
127
|
-
try:
|
128
|
-
if inspect.iscoroutinefunction(handler):
|
129
|
-
if len(params) == 1 and 'params' in params:
|
130
|
-
result = handler(params=kwargs)
|
131
|
-
else:
|
132
|
-
result = handler(**kwargs)
|
133
|
-
else:
|
134
|
-
if len(params) == 1 and 'params' in params:
|
135
|
-
result = loop.run_in_executor(None, lambda: handler(params=kwargs))
|
136
|
-
else:
|
137
|
-
result = loop.run_in_executor(None, lambda: handler(**kwargs))
|
138
|
-
if inspect.isawaitable(result):
|
139
|
-
return await result
|
140
|
-
else:
|
141
|
-
async def _return_sync():
|
142
|
-
return result
|
143
|
-
return await _return_sync()
|
144
|
-
except TypeError as e:
|
145
|
-
# Попробовать вызвать handler(params=kwargs), если ошибка связана с лишними именованными аргументами
|
146
|
-
if (len(params) == 1 and 'params' in params):
|
147
|
-
try:
|
148
|
-
if inspect.iscoroutinefunction(handler):
|
149
|
-
result = handler(params=kwargs)
|
150
|
-
else:
|
151
|
-
result = loop.run_in_executor(None, lambda: handler(params=kwargs))
|
152
|
-
if inspect.isawaitable(result):
|
153
|
-
return await result
|
154
|
-
else:
|
155
|
-
async def _return_sync():
|
156
|
-
return result
|
157
|
-
return await _return_sync()
|
158
|
-
except Exception:
|
159
|
-
pass
|
160
|
-
raise e
|
161
|
-
|
162
|
-
async def execute(self, command: str, **kwargs) -> Any:
|
163
|
-
"""
|
164
|
-
Executes a command with the specified parameters.
|
165
|
-
"""
|
166
|
-
if command not in self._handlers:
|
167
|
-
raise CommandNotFoundError(f"Command '{command}' not found")
|
168
|
-
handler = self._handlers[command]
|
169
|
-
try:
|
170
|
-
return await self._call_handler_always_awaitable(handler, kwargs)
|
171
|
-
except Exception as e:
|
172
|
-
logger.error(f"Error executing command '{command}': {str(e)}")
|
173
|
-
logger.debug(traceback.format_exc())
|
174
|
-
raise CommandExecutionError(f"Error executing command '{command}': {str(e)}")
|
175
|
-
|
176
|
-
def get_valid_commands(self) -> List[str]:
|
177
|
-
"""
|
178
|
-
Returns a list of all registered command names.
|
179
|
-
|
180
|
-
Returns:
|
181
|
-
List[str]: List of command names
|
182
|
-
"""
|
183
|
-
return list(self._handlers.keys())
|
184
|
-
|
185
|
-
def get_command_info(self, command: str) -> Optional[Dict[str, Any]]:
|
186
|
-
"""
|
187
|
-
Returns information about a command.
|
188
|
-
|
189
|
-
Args:
|
190
|
-
command: Command name
|
191
|
-
|
192
|
-
Returns:
|
193
|
-
Optional[Dict[str, Any]]: Command information or None if command not found
|
194
|
-
"""
|
195
|
-
if command not in self._metadata:
|
196
|
-
return None
|
197
|
-
|
198
|
-
return self._metadata[command]
|
199
|
-
|
200
|
-
def get_commands_info(self) -> Dict[str, Dict[str, Any]]:
|
201
|
-
"""
|
202
|
-
Returns information about all registered commands.
|
203
|
-
|
204
|
-
Returns:
|
205
|
-
Dict[str, Dict[str, Any]]: Dictionary {command_name: information}
|
206
|
-
"""
|
207
|
-
return self._metadata.copy()
|
208
|
-
|
209
|
-
def _help_command(self, params: Dict[str, Any] = None) -> Dict[str, Any]:
|
210
|
-
"""
|
211
|
-
Built-in help command for getting command information.
|
212
|
-
|
213
|
-
Args:
|
214
|
-
params: Command parameters
|
215
|
-
cmdname: Command name for detailed information
|
216
|
-
|
217
|
-
Returns:
|
218
|
-
Dict[str, Any]: Command help information
|
219
|
-
"""
|
220
|
-
if not params:
|
221
|
-
params = {}
|
222
|
-
# Если передан неправильный параметр 'command', возвращаем понятную ошибку
|
223
|
-
if "command" in params and params["command"]:
|
224
|
-
return {
|
225
|
-
"error": "Parameter 'command' is not supported. Use 'cmdname' instead.",
|
226
|
-
"hint": "Send params: {\"cmdname\": \"your_command\"}",
|
227
|
-
"example": {"jsonrpc": "2.0", "method": "help", "params": {"cmdname": "your_command"}, "id": 1}
|
228
|
-
}
|
229
|
-
# Если handler вызывается синхронно и возвращает coroutine, возвращаем явную ошибку
|
230
|
-
if inspect.iscoroutinefunction(self._help_command):
|
231
|
-
return {
|
232
|
-
"error": "Help handler must be awaited. Call as await dispatcher.execute('help', ...) in async context.",
|
233
|
-
"hint": "Use async endpoint or await the result in your code.",
|
234
|
-
"example": "result = await dispatcher.execute('help', cmdname='your_command')"
|
235
|
-
}
|
236
|
-
# If specific command is specified, return information only about it
|
237
|
-
if "cmdname" in params and params["cmdname"]:
|
238
|
-
command = params["cmdname"]
|
239
|
-
if command not in self._metadata:
|
240
|
-
return {
|
241
|
-
"error": f"Command '{command}' not found",
|
242
|
-
"available_commands": list(self._metadata.keys())
|
243
|
-
}
|
244
|
-
return {
|
245
|
-
"command": command,
|
246
|
-
"info": self._metadata[command]
|
247
|
-
}
|
248
|
-
|
249
|
-
# Otherwise return brief information about all commands
|
250
|
-
commands_info = {}
|
251
|
-
for cmd, info in self._metadata.items():
|
252
|
-
commands_info[cmd] = {
|
253
|
-
"summary": info["summary"],
|
254
|
-
"description": info["description"],
|
255
|
-
"params_count": len(info["params"])
|
256
|
-
}
|
257
|
-
|
258
|
-
return {
|
259
|
-
"commands": commands_info,
|
260
|
-
"total": len(commands_info),
|
261
|
-
"note": "Use the 'cmdname' parameter to get detailed information about a specific command"
|
262
|
-
}
|
@@ -1,141 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Analysis of MCP Proxy configuration generated by the adapter.
|
3
|
-
|
4
|
-
This script loads and analyzes the MCP Proxy configuration file
|
5
|
-
that was created by MCPProxyAdapter, and outputs structured
|
6
|
-
information about routes and tools.
|
7
|
-
|
8
|
-
Usage:
|
9
|
-
python examples/analyze_config.py
|
10
|
-
"""
|
11
|
-
import os
|
12
|
-
import json
|
13
|
-
import sys
|
14
|
-
from typing import Dict, Any, List
|
15
|
-
|
16
|
-
|
17
|
-
def load_config_file(config_path: str) -> Dict[str, Any]:
|
18
|
-
"""
|
19
|
-
Loads MCP Proxy configuration file.
|
20
|
-
|
21
|
-
Args:
|
22
|
-
config_path (str): Path to configuration file
|
23
|
-
|
24
|
-
Returns:
|
25
|
-
Dict[str, Any]: Loaded configuration
|
26
|
-
|
27
|
-
Raises:
|
28
|
-
FileNotFoundError: If file is not found
|
29
|
-
json.JSONDecodeError: If file is not valid JSON
|
30
|
-
"""
|
31
|
-
try:
|
32
|
-
with open(config_path, 'r', encoding='utf-8') as f:
|
33
|
-
return json.load(f)
|
34
|
-
except FileNotFoundError:
|
35
|
-
print(f"Error: Configuration file not found: {config_path}")
|
36
|
-
sys.exit(1)
|
37
|
-
except json.JSONDecodeError as e:
|
38
|
-
print(f"Error: File is not valid JSON: {e}")
|
39
|
-
sys.exit(1)
|
40
|
-
|
41
|
-
|
42
|
-
def print_routes(config: Dict[str, Any]) -> None:
|
43
|
-
"""
|
44
|
-
Prints route information from configuration.
|
45
|
-
|
46
|
-
Args:
|
47
|
-
config (Dict[str, Any]): MCP Proxy configuration
|
48
|
-
"""
|
49
|
-
if "routes" not in config:
|
50
|
-
print("No routes in configuration")
|
51
|
-
return
|
52
|
-
|
53
|
-
routes = config["routes"]
|
54
|
-
print(f"=== Routes ({len(routes)}) ===")
|
55
|
-
|
56
|
-
for i, route in enumerate(routes, 1):
|
57
|
-
print(f"\n{i}. Route:")
|
58
|
-
print(f" Endpoint: {route.get('endpoint', 'Not specified')}")
|
59
|
-
print(f" Method: {route.get('method', 'Not specified')}")
|
60
|
-
|
61
|
-
if "json_rpc" in route:
|
62
|
-
print(" Type: JSON-RPC")
|
63
|
-
if "params" in route["json_rpc"]:
|
64
|
-
print(f" Parameters: {route['json_rpc'].get('params', 'Not specified')}")
|
65
|
-
else:
|
66
|
-
print(" Type: Regular HTTP")
|
67
|
-
|
68
|
-
|
69
|
-
def print_tools(config: Dict[str, Any]) -> None:
|
70
|
-
"""
|
71
|
-
Prints tool information from configuration.
|
72
|
-
|
73
|
-
Args:
|
74
|
-
config (Dict[str, Any]): MCP Proxy configuration
|
75
|
-
"""
|
76
|
-
if "tools" not in config:
|
77
|
-
print("No tools in configuration")
|
78
|
-
return
|
79
|
-
|
80
|
-
tools = config["tools"]
|
81
|
-
print(f"\n=== Tools ({len(tools)}) ===")
|
82
|
-
|
83
|
-
for i, tool in enumerate(tools, 1):
|
84
|
-
print(f"\n{i}. Tool:")
|
85
|
-
print(f" Name: {tool.get('name', 'Not specified')}")
|
86
|
-
print(f" Description: {tool.get('description', 'Not specified')}")
|
87
|
-
|
88
|
-
if "parameters" in tool:
|
89
|
-
params = tool["parameters"]
|
90
|
-
required_params = params.get("required", [])
|
91
|
-
properties = params.get("properties", {})
|
92
|
-
|
93
|
-
print(f" Parameters ({len(properties)}):")
|
94
|
-
for param_name, param_info in properties.items():
|
95
|
-
required = "Required" if param_name in required_params else "Optional"
|
96
|
-
param_type = param_info.get("type", "Not specified")
|
97
|
-
description = param_info.get("description", "Not specified")
|
98
|
-
|
99
|
-
print(f" - {param_name} ({param_type}, {required}):")
|
100
|
-
print(f" {description}")
|
101
|
-
|
102
|
-
|
103
|
-
def analyze_config(config_path: str) -> None:
|
104
|
-
"""
|
105
|
-
Analyzes MCP Proxy configuration.
|
106
|
-
|
107
|
-
Args:
|
108
|
-
config_path (str): Path to configuration file
|
109
|
-
"""
|
110
|
-
config = load_config_file(config_path)
|
111
|
-
|
112
|
-
print("\n=== MCP Proxy Configuration Analysis ===")
|
113
|
-
print(f"File: {config_path}")
|
114
|
-
print(f"Version: {config.get('version', 'Not specified')}")
|
115
|
-
|
116
|
-
print_routes(config)
|
117
|
-
print_tools(config)
|
118
|
-
|
119
|
-
print("\n=== Summary ===")
|
120
|
-
num_routes = len(config.get("routes", []))
|
121
|
-
num_tools = len(config.get("tools", []))
|
122
|
-
|
123
|
-
print(f"Configuration contains {num_routes} routes and {num_tools} tools.")
|
124
|
-
|
125
|
-
if num_tools > 0 and num_routes > 0:
|
126
|
-
print("MCP Proxy is configured correctly and ready to use.")
|
127
|
-
else:
|
128
|
-
print("WARNING: Configuration may be incomplete or incorrect.")
|
129
|
-
|
130
|
-
|
131
|
-
def main():
|
132
|
-
"""Main script function."""
|
133
|
-
# Define path to configuration file
|
134
|
-
config_path = os.path.join(os.path.dirname(__file__), "mcp_proxy_config.json")
|
135
|
-
|
136
|
-
# Analyze configuration
|
137
|
-
analyze_config(config_path)
|
138
|
-
|
139
|
-
|
140
|
-
if __name__ == "__main__":
|
141
|
-
main()
|
@@ -1,155 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Example of basic MCPProxyAdapter integration with an existing FastAPI application.
|
3
|
-
"""
|
4
|
-
import logging
|
5
|
-
import os
|
6
|
-
import sys
|
7
|
-
from typing import Dict, Any, List, Optional
|
8
|
-
|
9
|
-
# Add parent directory to import path
|
10
|
-
current_dir = os.path.dirname(os.path.abspath(__file__))
|
11
|
-
parent_dir = os.path.dirname(current_dir)
|
12
|
-
if parent_dir not in sys.path:
|
13
|
-
sys.path.insert(0, parent_dir)
|
14
|
-
|
15
|
-
from fastapi import FastAPI, HTTPException, APIRouter
|
16
|
-
from pydantic import BaseModel
|
17
|
-
from mcp_proxy_adapter.adapter import MCPProxyAdapter, configure_logger
|
18
|
-
from mcp_proxy_adapter.registry import CommandRegistry
|
19
|
-
|
20
|
-
# Configure project logging
|
21
|
-
logging.basicConfig(
|
22
|
-
level=logging.INFO,
|
23
|
-
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
24
|
-
)
|
25
|
-
|
26
|
-
# Create project logger
|
27
|
-
project_logger = logging.getLogger("my_project")
|
28
|
-
project_logger.setLevel(logging.DEBUG)
|
29
|
-
|
30
|
-
# Configure adapter logger using project logger
|
31
|
-
adapter_logger = configure_logger(project_logger)
|
32
|
-
|
33
|
-
# Create FastAPI application
|
34
|
-
app = FastAPI(
|
35
|
-
title="My API with MCP Proxy Integration",
|
36
|
-
description="API with Command Registry integration, supporting MCP Proxy",
|
37
|
-
version="1.0.0"
|
38
|
-
)
|
39
|
-
|
40
|
-
# Define existing endpoints
|
41
|
-
router = APIRouter()
|
42
|
-
|
43
|
-
class Item(BaseModel):
|
44
|
-
name: str
|
45
|
-
price: float
|
46
|
-
is_active: bool = True
|
47
|
-
|
48
|
-
@router.get("/items", response_model=List[Item])
|
49
|
-
async def get_items():
|
50
|
-
"""Returns list of items."""
|
51
|
-
return [
|
52
|
-
{"name": "Item 1", "price": 10.5, "is_active": True},
|
53
|
-
{"name": "Item 2", "price": 20.0, "is_active": False},
|
54
|
-
{"name": "Item 3", "price": 30.0, "is_active": True},
|
55
|
-
]
|
56
|
-
|
57
|
-
@router.get("/items/{item_id}", response_model=Item)
|
58
|
-
async def get_item(item_id: int):
|
59
|
-
"""Returns item information by ID."""
|
60
|
-
items = [
|
61
|
-
{"name": "Item 1", "price": 10.5, "is_active": True},
|
62
|
-
{"name": "Item 2", "price": 20.0, "is_active": False},
|
63
|
-
{"name": "Item 3", "price": 30.0, "is_active": True},
|
64
|
-
]
|
65
|
-
|
66
|
-
if item_id < 1 or item_id > len(items):
|
67
|
-
raise HTTPException(status_code=404, detail="Item not found")
|
68
|
-
|
69
|
-
return items[item_id - 1]
|
70
|
-
|
71
|
-
# Add existing endpoints to application
|
72
|
-
app.include_router(router)
|
73
|
-
|
74
|
-
# Define commands for Command Registry
|
75
|
-
def list_items() -> List[Dict[str, Any]]:
|
76
|
-
"""
|
77
|
-
Returns list of all items.
|
78
|
-
|
79
|
-
Returns:
|
80
|
-
List[Dict[str, Any]]: List of items
|
81
|
-
"""
|
82
|
-
return [
|
83
|
-
{"name": "Item 1", "price": 10.5, "is_active": True},
|
84
|
-
{"name": "Item 2", "price": 20.0, "is_active": False},
|
85
|
-
{"name": "Item 3", "price": 30.0, "is_active": True},
|
86
|
-
]
|
87
|
-
|
88
|
-
def get_item_by_id(item_id: int) -> Dict[str, Any]:
|
89
|
-
"""
|
90
|
-
Returns item information by ID.
|
91
|
-
|
92
|
-
Args:
|
93
|
-
item_id: Item ID
|
94
|
-
|
95
|
-
Returns:
|
96
|
-
Dict[str, Any]: Item information
|
97
|
-
|
98
|
-
Raises:
|
99
|
-
ValueError: If item is not found
|
100
|
-
"""
|
101
|
-
items = list_items()
|
102
|
-
|
103
|
-
if item_id < 1 or item_id > len(items):
|
104
|
-
raise ValueError(f"Item with ID {item_id} not found")
|
105
|
-
|
106
|
-
return items[item_id - 1]
|
107
|
-
|
108
|
-
def search_items(query: str, min_price: Optional[float] = None, max_price: Optional[float] = None) -> List[Dict[str, Any]]:
|
109
|
-
"""
|
110
|
-
Searches for items by name and price range.
|
111
|
-
|
112
|
-
Args:
|
113
|
-
query: Search query for name
|
114
|
-
min_price: Minimum price (optional)
|
115
|
-
max_price: Maximum price (optional)
|
116
|
-
|
117
|
-
Returns:
|
118
|
-
List[Dict[str, Any]]: List of found items
|
119
|
-
"""
|
120
|
-
items = list_items()
|
121
|
-
|
122
|
-
# Filter by name
|
123
|
-
filtered_items = [item for item in items if query.lower() in item["name"].lower()]
|
124
|
-
|
125
|
-
# Filter by minimum price
|
126
|
-
if min_price is not None:
|
127
|
-
filtered_items = [item for item in filtered_items if item["price"] >= min_price]
|
128
|
-
|
129
|
-
# Filter by maximum price
|
130
|
-
if max_price is not None:
|
131
|
-
filtered_items = [item for item in filtered_items if item["price"] <= max_price]
|
132
|
-
|
133
|
-
return filtered_items
|
134
|
-
|
135
|
-
# Create CommandRegistry instance
|
136
|
-
registry = CommandRegistry()
|
137
|
-
|
138
|
-
# Register commands
|
139
|
-
registry.register_command("list_items", list_items)
|
140
|
-
registry.register_command("get_item", get_item_by_id)
|
141
|
-
registry.register_command("search_items", search_items)
|
142
|
-
|
143
|
-
# Create MCP Proxy adapter
|
144
|
-
adapter = MCPProxyAdapter(registry)
|
145
|
-
|
146
|
-
# Register endpoints in existing application
|
147
|
-
adapter.register_endpoints(app)
|
148
|
-
|
149
|
-
# Save configuration for MCP Proxy
|
150
|
-
adapter.save_config_to_file("mcp_proxy_config.json")
|
151
|
-
|
152
|
-
# Entry point for running the application
|
153
|
-
if __name__ == "__main__":
|
154
|
-
import uvicorn
|
155
|
-
uvicorn.run(app, host="0.0.0.0", port=8000)
|
@@ -1,69 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Docstring and Schema Example for MCPProxyAdapter
|
3
|
-
|
4
|
-
- How to write docstrings for commands
|
5
|
-
- How docstrings are used in OpenAPI/schema
|
6
|
-
- Best practices for documenting parameters and return values
|
7
|
-
|
8
|
-
Run:
|
9
|
-
python examples/docstring_and_schema_example.py
|
10
|
-
"""
|
11
|
-
import os
|
12
|
-
import sys
|
13
|
-
import asyncio
|
14
|
-
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
|
15
|
-
from mcp_proxy_adapter.adapter import MCPProxyAdapter
|
16
|
-
|
17
|
-
class MyRegistry:
|
18
|
-
def __init__(self):
|
19
|
-
self.dispatcher = self
|
20
|
-
self.commands = {"sum": self.sum_numbers}
|
21
|
-
self.commands_info = {
|
22
|
-
"sum": {
|
23
|
-
"description": self.sum_numbers.__doc__,
|
24
|
-
"params": {
|
25
|
-
"a": {"type": "integer", "description": "First number", "required": True},
|
26
|
-
"b": {"type": "integer", "description": "Second number", "required": True}
|
27
|
-
}
|
28
|
-
}
|
29
|
-
}
|
30
|
-
def get_valid_commands(self):
|
31
|
-
return list(self.commands.keys())
|
32
|
-
def get_command_info(self, command):
|
33
|
-
return self.commands_info.get(command)
|
34
|
-
def get_commands_info(self):
|
35
|
-
return self.commands_info
|
36
|
-
def execute(self, command, **params):
|
37
|
-
if command == "sum":
|
38
|
-
return self.sum_numbers(**params)
|
39
|
-
raise KeyError(f"Unknown command: {command}")
|
40
|
-
def add_generator(self, generator):
|
41
|
-
pass
|
42
|
-
def sum_numbers(self, a: int, b: int) -> int:
|
43
|
-
"""
|
44
|
-
Returns the sum of two numbers.
|
45
|
-
|
46
|
-
Args:
|
47
|
-
a (int): First number
|
48
|
-
b (int): Second number
|
49
|
-
|
50
|
-
Returns:
|
51
|
-
int: The sum of a and b
|
52
|
-
"""
|
53
|
-
return a + b
|
54
|
-
|
55
|
-
if __name__ == "__main__":
|
56
|
-
registry = MyRegistry()
|
57
|
-
adapter = MCPProxyAdapter(registry)
|
58
|
-
# Print OpenAPI schema (simulated)
|
59
|
-
schema = adapter.generate_mcp_proxy_config()
|
60
|
-
print("=== Tool description from docstring ===")
|
61
|
-
print(schema.tools[0].description)
|
62
|
-
|
63
|
-
# Call sync handler
|
64
|
-
result_sync = registry.execute('sum', a=10, b=1)
|
65
|
-
print(result_sync) # 11
|
66
|
-
|
67
|
-
# Call sync handler (ещё раз)
|
68
|
-
result_sync2 = registry.execute('sum', a=10, b=10)
|
69
|
-
print(result_sync2) # 20
|
@@ -1,72 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Extension Example for MCPProxyAdapter
|
3
|
-
|
4
|
-
- How to add custom commands
|
5
|
-
- How to extend help logic
|
6
|
-
- How to customize error handling
|
7
|
-
|
8
|
-
Run:
|
9
|
-
python examples/extension_example.py
|
10
|
-
"""
|
11
|
-
import os
|
12
|
-
import sys
|
13
|
-
import asyncio
|
14
|
-
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
|
15
|
-
from mcp_proxy_adapter.adapter import MCPProxyAdapter
|
16
|
-
|
17
|
-
class MyRegistry:
|
18
|
-
def __init__(self):
|
19
|
-
self.dispatcher = self
|
20
|
-
self.commands = {"ping": self.ping, "help": self.help_command}
|
21
|
-
self.commands_info = {
|
22
|
-
"ping": {"description": "Ping command (returns pong)", "params": {}},
|
23
|
-
"help": {"description": "Show help for commands", "params": {"cmdname": {"type": "string", "description": "Command name", "required": False}}}
|
24
|
-
}
|
25
|
-
def get_valid_commands(self):
|
26
|
-
return list(self.commands.keys())
|
27
|
-
def get_command_info(self, command):
|
28
|
-
return self.commands_info.get(command)
|
29
|
-
def get_commands_info(self):
|
30
|
-
return self.commands_info
|
31
|
-
def execute(self, *args, **params):
|
32
|
-
if args:
|
33
|
-
command = args[0]
|
34
|
-
params = {k: v for k, v in params.items()}
|
35
|
-
else:
|
36
|
-
command = params.pop("cmdname", None)
|
37
|
-
if command == "ping":
|
38
|
-
return self.ping()
|
39
|
-
if command == "help":
|
40
|
-
return self.help_command(**params)
|
41
|
-
raise KeyError(f"Unknown command: {command}")
|
42
|
-
def add_generator(self, generator):
|
43
|
-
pass
|
44
|
-
def ping(self):
|
45
|
-
"""Ping command."""
|
46
|
-
return {"result": "pong"}
|
47
|
-
def help_command(self, cmdname: str = None):
|
48
|
-
"""Custom help logic: returns info for command or all commands."""
|
49
|
-
if not cmdname:
|
50
|
-
return {"commands": list(self.commands_info.keys())}
|
51
|
-
if cmdname in self.commands_info:
|
52
|
-
return {"command": cmdname, "info": self.commands_info[cmdname]}
|
53
|
-
return {"error": f"Command '{cmdname}' not found"}
|
54
|
-
|
55
|
-
if __name__ == "__main__":
|
56
|
-
registry = MyRegistry()
|
57
|
-
adapter = MCPProxyAdapter(registry)
|
58
|
-
# Call sync handler
|
59
|
-
result_sync = registry.execute("ping")
|
60
|
-
print(result_sync) # Ping
|
61
|
-
|
62
|
-
# Call help (all)
|
63
|
-
result_help_all = registry.execute("help")
|
64
|
-
print("Help (all)", result_help_all)
|
65
|
-
|
66
|
-
# Call help (ping)
|
67
|
-
result_help_ping = registry.execute("help", cmdname="ping")
|
68
|
-
print("Help (ping)", result_help_ping)
|
69
|
-
|
70
|
-
# Call help (notfound)
|
71
|
-
result_help_notfound = registry.execute("help", cmdname="notfound")
|
72
|
-
print("Help (notfound)", result_help_notfound)
|