mcp-proxy-adapter 2.1.11__py3-none-any.whl → 2.1.13__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/analyzers/__init__.py +1 -14
- mcp_proxy_adapter/dispatchers/__init__.py +1 -14
- mcp_proxy_adapter/dispatchers/json_rpc_dispatcher.py +76 -40
- mcp_proxy_adapter/examples/basic_integration.py +2 -8
- mcp_proxy_adapter/examples/help_usage.py +10 -10
- mcp_proxy_adapter/examples/openapi_server.py +9 -8
- mcp_proxy_adapter-2.1.13.dist-info/METADATA +341 -0
- mcp_proxy_adapter-2.1.13.dist-info/RECORD +19 -0
- {mcp_proxy_adapter-2.1.11.dist-info → mcp_proxy_adapter-2.1.13.dist-info}/WHEEL +1 -1
- mcp_proxy_adapter/__init__.py +0 -17
- mcp_proxy_adapter/adapter.py +0 -697
- mcp_proxy_adapter/analyzers/docstring_analyzer.py +0 -199
- mcp_proxy_adapter/analyzers/type_analyzer.py +0 -151
- mcp_proxy_adapter/dispatchers/base_dispatcher.py +0 -85
- mcp_proxy_adapter/models.py +0 -47
- mcp_proxy_adapter/registry.py +0 -439
- mcp_proxy_adapter/schema.py +0 -257
- mcp_proxy_adapter/validators/docstring_validator.py +0 -75
- mcp_proxy_adapter/validators/metadata_validator.py +0 -76
- mcp_proxy_adapter-2.1.11.dist-info/METADATA +0 -402
- mcp_proxy_adapter-2.1.11.dist-info/RECORD +0 -29
- {mcp_proxy_adapter-2.1.11.dist-info → mcp_proxy_adapter-2.1.13.dist-info}/licenses/LICENSE +0 -0
- {mcp_proxy_adapter-2.1.11.dist-info → mcp_proxy_adapter-2.1.13.dist-info}/top_level.txt +0 -0
@@ -1,14 +1 @@
|
|
1
|
-
|
2
|
-
Analyzers for extracting metadata from functions and docstrings.
|
3
|
-
|
4
|
-
This module contains classes for analyzing type annotations and docstrings
|
5
|
-
of Python functions to automatically extract metadata for commands.
|
6
|
-
"""
|
7
|
-
|
8
|
-
from .type_analyzer import TypeAnalyzer
|
9
|
-
from .docstring_analyzer import DocstringAnalyzer
|
10
|
-
|
11
|
-
__all__ = [
|
12
|
-
'TypeAnalyzer',
|
13
|
-
'DocstringAnalyzer',
|
14
|
-
]
|
1
|
+
|
@@ -1,14 +1 @@
|
|
1
|
-
|
2
|
-
Command dispatchers for registering and executing commands.
|
3
|
-
|
4
|
-
This module contains base classes and implementations of command dispatchers
|
5
|
-
that are responsible for registering and executing commands.
|
6
|
-
"""
|
7
|
-
|
8
|
-
from .base_dispatcher import BaseDispatcher
|
9
|
-
from .json_rpc_dispatcher import JsonRpcDispatcher
|
10
|
-
|
11
|
-
__all__ = [
|
12
|
-
'BaseDispatcher',
|
13
|
-
'JsonRpcDispatcher',
|
14
|
-
]
|
1
|
+
|
@@ -1,14 +1,20 @@
|
|
1
1
|
"""
|
2
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.
|
3
6
|
"""
|
4
7
|
from typing import Dict, Any, Callable, List, Optional, Union
|
5
8
|
import inspect
|
6
9
|
import logging
|
7
10
|
import traceback
|
8
11
|
from .base_dispatcher import BaseDispatcher
|
12
|
+
import asyncio
|
9
13
|
|
10
14
|
logger = logging.getLogger("command_registry")
|
11
15
|
|
16
|
+
print('[DEBUG] LOADED json_rpc_dispatcher.py')
|
17
|
+
|
12
18
|
class CommandError(Exception):
|
13
19
|
"""Base class for command errors"""
|
14
20
|
pass
|
@@ -27,6 +33,34 @@ class JsonRpcDispatcher(BaseDispatcher):
|
|
27
33
|
|
28
34
|
Implements the BaseDispatcher interface for handling commands in JSON-RPC 2.0 format.
|
29
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
|
30
64
|
"""
|
31
65
|
|
32
66
|
def __init__(self):
|
@@ -38,10 +72,14 @@ class JsonRpcDispatcher(BaseDispatcher):
|
|
38
72
|
self.register_handler(
|
39
73
|
command="help",
|
40
74
|
handler=self._help_command,
|
41
|
-
description=
|
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
|
+
),
|
42
80
|
summary="Command help",
|
43
81
|
params={
|
44
|
-
"
|
82
|
+
"command": {
|
45
83
|
"type": "string",
|
46
84
|
"description": "Command name for detailed information",
|
47
85
|
"required": False
|
@@ -82,43 +120,41 @@ class JsonRpcDispatcher(BaseDispatcher):
|
|
82
120
|
|
83
121
|
logger.debug(f"Registered command: {command}")
|
84
122
|
|
85
|
-
def
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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 Exception as e:
|
145
|
+
raise e
|
146
|
+
|
147
|
+
async def execute(self, command: str, **kwargs) -> Any:
|
100
148
|
# Check if command exists
|
101
149
|
if command not in self._handlers:
|
102
150
|
raise CommandNotFoundError(f"Command '{command}' not found")
|
103
|
-
|
104
151
|
handler = self._handlers[command]
|
105
|
-
|
106
152
|
try:
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
# If function accepts params dictionary, pass all parameters in it
|
111
|
-
if len(sig.parameters) == 1 and list(sig.parameters.keys())[0] == 'params':
|
112
|
-
return handler(params=kwargs)
|
113
|
-
|
114
|
-
# Otherwise pass parameters as named arguments
|
115
|
-
return handler(**kwargs)
|
153
|
+
result = await self._call_handler_always_awaitable(handler, kwargs)
|
154
|
+
return result
|
116
155
|
except Exception as e:
|
117
|
-
# Log the error
|
118
156
|
logger.error(f"Error executing command '{command}': {str(e)}")
|
119
157
|
logger.debug(traceback.format_exc())
|
120
|
-
|
121
|
-
# Re-raise the exception
|
122
158
|
raise CommandExecutionError(f"Error executing command '{command}': {str(e)}")
|
123
159
|
|
124
160
|
def get_valid_commands(self) -> List[str]:
|
@@ -160,7 +196,7 @@ class JsonRpcDispatcher(BaseDispatcher):
|
|
160
196
|
|
161
197
|
Args:
|
162
198
|
params: Command parameters
|
163
|
-
|
199
|
+
command: Command name for detailed information
|
164
200
|
|
165
201
|
Returns:
|
166
202
|
Dict[str, Any]: Command help information
|
@@ -168,18 +204,18 @@ class JsonRpcDispatcher(BaseDispatcher):
|
|
168
204
|
if not params:
|
169
205
|
params = {}
|
170
206
|
|
171
|
-
#
|
172
|
-
if "
|
173
|
-
|
174
|
-
if
|
207
|
+
# If specific command is specified, return information only about it
|
208
|
+
if "command" in params and params["command"]:
|
209
|
+
command = params["command"]
|
210
|
+
if command not in self._metadata:
|
175
211
|
return {
|
176
|
-
"error": f"Command '{
|
212
|
+
"error": f"Command '{command}' not found",
|
177
213
|
"available_commands": list(self._metadata.keys())
|
178
214
|
}
|
179
215
|
|
180
216
|
return {
|
181
|
-
"
|
182
|
-
"info": self._metadata[
|
217
|
+
"command": command,
|
218
|
+
"info": self._metadata[command]
|
183
219
|
}
|
184
220
|
|
185
221
|
# Otherwise return brief information about all commands
|
@@ -194,5 +230,5 @@ class JsonRpcDispatcher(BaseDispatcher):
|
|
194
230
|
return {
|
195
231
|
"commands": commands_info,
|
196
232
|
"total": len(commands_info),
|
197
|
-
"note": "
|
233
|
+
"note": "Use the 'command' parameter to get detailed information about a specific command"
|
198
234
|
}
|
@@ -14,14 +14,8 @@ if parent_dir not in sys.path:
|
|
14
14
|
|
15
15
|
from fastapi import FastAPI, HTTPException, APIRouter
|
16
16
|
from pydantic import BaseModel
|
17
|
-
|
18
|
-
|
19
|
-
try:
|
20
|
-
from mcp_proxy_adapter.adapter import MCPProxyAdapter, configure_logger
|
21
|
-
from mcp_proxy_adapter.registry import CommandRegistry
|
22
|
-
except ImportError:
|
23
|
-
from src.adapter import MCPProxyAdapter, configure_logger
|
24
|
-
from src.registry import CommandRegistry
|
17
|
+
from mcp_proxy_adapter.adapter import MCPProxyAdapter, configure_logger
|
18
|
+
from mcp_proxy_adapter.registry import CommandRegistry
|
25
19
|
|
26
20
|
# Configure project logging
|
27
21
|
logging.basicConfig(
|
@@ -24,29 +24,29 @@ registry = MockRegistry()
|
|
24
24
|
adapter = MCPProxyAdapter(registry)
|
25
25
|
|
26
26
|
# --- Best practice: always check if 'help' is in commands ---
|
27
|
-
def call_help(
|
27
|
+
def call_help(command: str = None) -> Dict[str, Any]:
|
28
28
|
"""Call help command with or without parameter."""
|
29
29
|
dispatcher = registry.dispatcher
|
30
30
|
if "help" in dispatcher.get_valid_commands():
|
31
|
-
if
|
31
|
+
if command:
|
32
32
|
try:
|
33
|
-
return dispatcher.help_command(
|
33
|
+
return dispatcher.help_command(command=command)
|
34
34
|
except Exception as e:
|
35
35
|
print(f"Project help failed: {e}. Fallback to adapter help.")
|
36
|
-
return adapter_help(
|
36
|
+
return adapter_help(command)
|
37
37
|
else:
|
38
38
|
return dispatcher.help_command()
|
39
39
|
else:
|
40
|
-
return adapter_help(
|
40
|
+
return adapter_help(command)
|
41
41
|
|
42
|
-
def adapter_help(
|
42
|
+
def adapter_help(command: str = None) -> Dict[str, Any]:
|
43
43
|
"""Fallback: call adapter's help (simulate)."""
|
44
44
|
dispatcher = registry.dispatcher
|
45
|
-
if not
|
45
|
+
if not command:
|
46
46
|
return {"source": "adapter", "commands": dispatcher.get_valid_commands()}
|
47
|
-
if
|
48
|
-
return {"source": "adapter", "
|
49
|
-
return {"source": "adapter", "error": f"Command '{
|
47
|
+
if command in dispatcher.get_valid_commands():
|
48
|
+
return {"source": "adapter", "command": command, "info": {"description": "Adapter help for command"}}
|
49
|
+
return {"source": "adapter", "error": f"Command '{command}' not found (adapter)", "available_commands": dispatcher.get_valid_commands()}
|
50
50
|
|
51
51
|
if __name__ == "__main__":
|
52
52
|
print("=== Project help (no param) ===")
|
@@ -171,7 +171,7 @@ class MockDispatcher:
|
|
171
171
|
"help": {
|
172
172
|
"description": "Show information about available commands or a specific command.",
|
173
173
|
"params": {
|
174
|
-
"
|
174
|
+
"command": {
|
175
175
|
"type": "string",
|
176
176
|
"description": "Command name for detailed info",
|
177
177
|
"required": False
|
@@ -278,18 +278,19 @@ class MockDispatcher:
|
|
278
278
|
|
279
279
|
def help_command(self, **params):
|
280
280
|
"""Return info about all commands or a specific command."""
|
281
|
-
|
282
|
-
|
283
|
-
|
281
|
+
# Если в будущем появится пользовательская команда help, можно реализовать её здесь
|
282
|
+
command = params.get("command")
|
283
|
+
if command:
|
284
|
+
info = self.commands_info.get(command)
|
284
285
|
if info:
|
285
|
-
return {"
|
286
|
+
return {"command": command, "info": info}
|
286
287
|
else:
|
287
|
-
return {"error": f"Command '{
|
288
|
-
# Если параметр
|
288
|
+
return {"error": f"Command '{command}' not found", "available_commands": list(self.commands_info.keys())}
|
289
|
+
# Если параметр command не указан, возвращаем краткую информацию обо всех
|
289
290
|
return {
|
290
291
|
"commands": {cmd: {"description": info["description"], "params": info["params"]} for cmd, info in self.commands_info.items()},
|
291
292
|
"total": len(self.commands_info),
|
292
|
-
"note": "Use the '
|
293
|
+
"note": "Use the 'command' parameter to get detailed information about a specific command"
|
293
294
|
}
|
294
295
|
|
295
296
|
# --- Создание registry и FastAPI-приложения на верхнем уровне ---
|
@@ -0,0 +1,341 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: mcp-proxy-adapter
|
3
|
+
Version: 2.1.13
|
4
|
+
Summary: Adapter for exposing Command Registry commands as tools for AI models via MCP Proxy.
|
5
|
+
Home-page: https://github.com/vasilyvz/mcp-proxy-adapter
|
6
|
+
Author: Vasiliy VZ
|
7
|
+
Author-email: Vasiliy VZ <vasilyvz@example.com>
|
8
|
+
License: MIT
|
9
|
+
Project-URL: Homepage, https://github.com/vasilyvz/mcp-proxy-adapter
|
10
|
+
Project-URL: Bug Tracker, https://github.com/vasilyvz/mcp-proxy-adapter/issues
|
11
|
+
Project-URL: Documentation, https://github.com/vasilyvz/mcp-proxy-adapter/tree/main/docs
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
13
|
+
Classifier: Intended Audience :: Developers
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
20
|
+
Requires-Python: >=3.9, <4
|
21
|
+
Description-Content-Type: text/markdown
|
22
|
+
License-File: LICENSE
|
23
|
+
Requires-Dist: fastapi<1.0.0,>=0.95.0
|
24
|
+
Requires-Dist: pydantic>=2.0.0
|
25
|
+
Requires-Dist: uvicorn<1.0.0,>=0.22.0
|
26
|
+
Requires-Dist: docstring-parser<1.0.0,>=0.15
|
27
|
+
Requires-Dist: typing-extensions<5.0.0,>=4.5.0
|
28
|
+
Dynamic: author
|
29
|
+
Dynamic: home-page
|
30
|
+
Dynamic: license-file
|
31
|
+
Dynamic: requires-python
|
32
|
+
|
33
|
+
# MCP Proxy Adapter
|
34
|
+
|
35
|
+
Adapter for integrating [Command Registry](docs/README.md) with MCP Proxy, allowing you to use commands as tools for AI models.
|
36
|
+
|
37
|
+
## Overview
|
38
|
+
|
39
|
+
MCP Proxy Adapter transforms commands registered in the Command Registry into a format compatible with MCP Proxy. This enables:
|
40
|
+
|
41
|
+
1. Using existing commands as tools for AI models
|
42
|
+
2. Creating a hybrid REST/JSON-RPC API for command execution
|
43
|
+
3. Automatic generation of OpenAPI schemas optimized for MCP Proxy
|
44
|
+
4. Managing tool metadata for better AI system integration
|
45
|
+
|
46
|
+
## Installation
|
47
|
+
|
48
|
+
```bash
|
49
|
+
pip install mcp-proxy-adapter
|
50
|
+
```
|
51
|
+
|
52
|
+
## Quick Start
|
53
|
+
|
54
|
+
```python
|
55
|
+
from mcp_proxy_adapter import MCPProxyAdapter, CommandRegistry
|
56
|
+
from fastapi import FastAPI
|
57
|
+
|
58
|
+
# Create a command registry instance
|
59
|
+
registry = CommandRegistry()
|
60
|
+
|
61
|
+
# Register commands
|
62
|
+
def calculate_total(prices: list[float], discount: float = 0.0) -> float:
|
63
|
+
"""
|
64
|
+
Calculates the total price with discount.
|
65
|
+
Args:
|
66
|
+
prices: List of item prices
|
67
|
+
discount: Discount percentage (0-100)
|
68
|
+
Returns:
|
69
|
+
Total price with discount
|
70
|
+
"""
|
71
|
+
subtotal = sum(prices)
|
72
|
+
return subtotal * (1 - discount / 100)
|
73
|
+
|
74
|
+
registry.register_command("calculate_total", calculate_total)
|
75
|
+
|
76
|
+
# Create FastAPI app
|
77
|
+
app = FastAPI()
|
78
|
+
|
79
|
+
# Create and configure MCP Proxy adapter
|
80
|
+
adapter = MCPProxyAdapter(registry)
|
81
|
+
|
82
|
+
# Register endpoints in FastAPI app
|
83
|
+
adapter.register_endpoints(app)
|
84
|
+
|
85
|
+
# Generate and save MCP Proxy config
|
86
|
+
adapter.save_config_to_file("mcp_proxy_config.json")
|
87
|
+
```
|
88
|
+
|
89
|
+
## Supported Request Formats
|
90
|
+
|
91
|
+
The adapter supports three request formats for command execution:
|
92
|
+
|
93
|
+
### 1. JSON-RPC format
|
94
|
+
|
95
|
+
```json
|
96
|
+
{
|
97
|
+
"jsonrpc": "2.0",
|
98
|
+
"method": "command_name",
|
99
|
+
"params": {
|
100
|
+
"param1": "value1",
|
101
|
+
"param2": "value2"
|
102
|
+
},
|
103
|
+
"id": 1
|
104
|
+
}
|
105
|
+
```
|
106
|
+
|
107
|
+
Example request to `/cmd` endpoint:
|
108
|
+
|
109
|
+
```bash
|
110
|
+
curl -X POST -H "Content-Type: application/json" -d '{
|
111
|
+
"jsonrpc": "2.0",
|
112
|
+
"method": "calculate_total",
|
113
|
+
"params": {
|
114
|
+
"prices": [100, 200, 300],
|
115
|
+
"discount": 10
|
116
|
+
},
|
117
|
+
"id": 1
|
118
|
+
}' http://localhost:8000/cmd
|
119
|
+
```
|
120
|
+
|
121
|
+
Response:
|
122
|
+
|
123
|
+
```json
|
124
|
+
{
|
125
|
+
"jsonrpc": "2.0",
|
126
|
+
"result": 540.0,
|
127
|
+
"id": 1
|
128
|
+
}
|
129
|
+
```
|
130
|
+
|
131
|
+
### 2. MCP Proxy format
|
132
|
+
|
133
|
+
```json
|
134
|
+
{
|
135
|
+
"command": "command_name",
|
136
|
+
"params": {
|
137
|
+
"param1": "value1",
|
138
|
+
"param2": "value2"
|
139
|
+
}
|
140
|
+
}
|
141
|
+
```
|
142
|
+
|
143
|
+
Example request:
|
144
|
+
|
145
|
+
```bash
|
146
|
+
curl -X POST -H "Content-Type: application/json" -d '{
|
147
|
+
"command": "calculate_total",
|
148
|
+
"params": {
|
149
|
+
"prices": [100, 200, 300],
|
150
|
+
"discount": 10
|
151
|
+
}
|
152
|
+
}' http://localhost:8000/cmd
|
153
|
+
```
|
154
|
+
|
155
|
+
Response:
|
156
|
+
|
157
|
+
```json
|
158
|
+
{
|
159
|
+
"result": 540.0
|
160
|
+
}
|
161
|
+
```
|
162
|
+
|
163
|
+
### 3. Params-only format
|
164
|
+
|
165
|
+
```json
|
166
|
+
{
|
167
|
+
"params": {
|
168
|
+
"command": "command_name",
|
169
|
+
"param1": "value1",
|
170
|
+
"param2": "value2"
|
171
|
+
}
|
172
|
+
}
|
173
|
+
```
|
174
|
+
|
175
|
+
or
|
176
|
+
|
177
|
+
```json
|
178
|
+
{
|
179
|
+
"params": {
|
180
|
+
"query": "command_name",
|
181
|
+
"param1": "value1",
|
182
|
+
"param2": "value2"
|
183
|
+
}
|
184
|
+
}
|
185
|
+
```
|
186
|
+
|
187
|
+
Example request:
|
188
|
+
|
189
|
+
```bash
|
190
|
+
curl -X POST -H "Content-Type: application/json" -d '{
|
191
|
+
"params": {
|
192
|
+
"command": "calculate_total",
|
193
|
+
"prices": [100, 200, 300],
|
194
|
+
"discount": 10
|
195
|
+
}
|
196
|
+
}' http://localhost:8000/cmd
|
197
|
+
```
|
198
|
+
|
199
|
+
Response:
|
200
|
+
|
201
|
+
```json
|
202
|
+
{
|
203
|
+
"result": 540.0
|
204
|
+
}
|
205
|
+
```
|
206
|
+
|
207
|
+
## Full Example: Integration with FastAPI
|
208
|
+
|
209
|
+
```python
|
210
|
+
import logging
|
211
|
+
from fastapi import FastAPI, APIRouter
|
212
|
+
from mcp_proxy_adapter import CommandRegistry, MCPProxyAdapter, configure_logger
|
213
|
+
|
214
|
+
# Configure logging
|
215
|
+
logging.basicConfig(level=logging.INFO)
|
216
|
+
project_logger = logging.getLogger("my_project")
|
217
|
+
|
218
|
+
# Create FastAPI app
|
219
|
+
app = FastAPI(title="My API with MCP Proxy Integration")
|
220
|
+
|
221
|
+
# Create existing API router
|
222
|
+
router = APIRouter()
|
223
|
+
|
224
|
+
@router.get("/items")
|
225
|
+
async def get_items():
|
226
|
+
"""Returns a list of items."""
|
227
|
+
return [
|
228
|
+
{"id": 1, "name": "Smartphone X", "price": 999.99},
|
229
|
+
{"id": 2, "name": "Laptop Y", "price": 1499.99},
|
230
|
+
]
|
231
|
+
|
232
|
+
app.include_router(router)
|
233
|
+
|
234
|
+
# Register commands
|
235
|
+
registry = CommandRegistry()
|
236
|
+
|
237
|
+
def get_discounted_price(price: float, discount: float = 0.0) -> float:
|
238
|
+
"""
|
239
|
+
Returns the price after applying a discount.
|
240
|
+
"""
|
241
|
+
return price * (1 - discount / 100)
|
242
|
+
|
243
|
+
registry.register_command("get_discounted_price", get_discounted_price)
|
244
|
+
|
245
|
+
# Create and register MCP Proxy adapter
|
246
|
+
adapter = MCPProxyAdapter(registry)
|
247
|
+
adapter.register_endpoints(app)
|
248
|
+
|
249
|
+
# Save MCP Proxy config
|
250
|
+
adapter.save_config_to_file("mcp_proxy_config.json")
|
251
|
+
```
|
252
|
+
|
253
|
+
## Features
|
254
|
+
- Universal JSON-RPC endpoint for command execution
|
255
|
+
- Automatic OpenAPI schema generation and optimization for MCP Proxy
|
256
|
+
- Tool metadata for AI models
|
257
|
+
- Customizable endpoints and logging
|
258
|
+
- Full test coverage and examples
|
259
|
+
|
260
|
+
## FAQ
|
261
|
+
|
262
|
+
**Q: Почему не удаётся использовать декоратор @registry.command для регистрации команд?**
|
263
|
+
A: В последних версиях пакета декоратор @registry.command больше не поддерживается. Теперь команды регистрируются только явно, через вызов метода:
|
264
|
+
|
265
|
+
```python
|
266
|
+
registry.register_command("имя_команды", функция)
|
267
|
+
```
|
268
|
+
|
269
|
+
**Q: Почему не удаётся импортировать CommandRegistry напрямую из mcp_proxy_adapter?**
|
270
|
+
A: Класс CommandRegistry находится в подмодуле registry. Используйте:
|
271
|
+
|
272
|
+
```python
|
273
|
+
from mcp_proxy_adapter.registry import CommandRegistry
|
274
|
+
```
|
275
|
+
|
276
|
+
**Q: Какой способ импорта MCPProxyAdapter и configure_logger?**
|
277
|
+
A: Импортируйте их из подмодуля adapter:
|
278
|
+
|
279
|
+
```python
|
280
|
+
from mcp_proxy_adapter.adapter import MCPProxyAdapter, configure_logger
|
281
|
+
```
|
282
|
+
|
283
|
+
**Q: Как добавить свою команду?**
|
284
|
+
A: Зарегистрируйте функцию через registry.register_command или декоратор (см. примеры выше).
|
285
|
+
|
286
|
+
**Q: Как получить OpenAPI-схему?**
|
287
|
+
A: После регистрации адаптера вызовите /openapi.json в вашем FastAPI-приложении.
|
288
|
+
|
289
|
+
**Q: Какой формат запроса поддерживается?**
|
290
|
+
A: JSON-RPC, MCP Proxy и params-only (см. раздел Supported Request Formats).
|
291
|
+
|
292
|
+
## HOWTO
|
293
|
+
|
294
|
+
**Как интегрировать MCP Proxy Adapter с FastAPI:**
|
295
|
+
1. Установите пакет:
|
296
|
+
```bash
|
297
|
+
pip install mcp-proxy-adapter
|
298
|
+
```
|
299
|
+
2. Импортируйте нужные классы:
|
300
|
+
```python
|
301
|
+
from mcp_proxy_adapter.registry import CommandRegistry
|
302
|
+
from mcp_proxy_adapter.adapter import MCPProxyAdapter
|
303
|
+
```
|
304
|
+
3. Зарегистрируйте команды и настройте FastAPI:
|
305
|
+
```python
|
306
|
+
registry = CommandRegistry()
|
307
|
+
registry.register_command("my_command", my_function)
|
308
|
+
app = FastAPI()
|
309
|
+
adapter = MCPProxyAdapter(registry)
|
310
|
+
adapter.register_endpoints(app)
|
311
|
+
```
|
312
|
+
4. (Опционально) Сохраните конфиг для MCP Proxy:
|
313
|
+
```python
|
314
|
+
adapter.save_config_to_file("mcp_proxy_config.json")
|
315
|
+
```
|
316
|
+
|
317
|
+
**Как добавить поддержку кастомного логгера:**
|
318
|
+
```python
|
319
|
+
import logging
|
320
|
+
from mcp_proxy_adapter.adapter import configure_logger
|
321
|
+
logger = logging.getLogger("my_project")
|
322
|
+
adapter_logger = configure_logger(logger)
|
323
|
+
```
|
324
|
+
|
325
|
+
**Как получить список всех команд через API:**
|
326
|
+
Вызовите GET `/api/commands` на вашем сервере FastAPI.
|
327
|
+
|
328
|
+
## License
|
329
|
+
MIT
|
330
|
+
|
331
|
+
## Documentation
|
332
|
+
See [docs/](docs/) for detailed guides, architecture, and examples.
|
333
|
+
|
334
|
+
## CI/CD & PyPI automation
|
335
|
+
|
336
|
+
This project uses GitHub Actions for continuous integration and automated publishing to PyPI.
|
337
|
+
|
338
|
+
- All tests are run on every push and pull request.
|
339
|
+
- On push of a new tag (vX.Y.Z), the package is built and published to PyPI automatically.
|
340
|
+
|
341
|
+
See `.github/workflows/publish.yml` for details.
|
@@ -0,0 +1,19 @@
|
|
1
|
+
mcp_proxy_adapter/testing_utils.py,sha256=RWjQFNSUtVkeP0qNzp6_jrT6_tub3w_052DrRmvxVk0,4243
|
2
|
+
mcp_proxy_adapter/analyzers/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
|
3
|
+
mcp_proxy_adapter/dispatchers/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
|
4
|
+
mcp_proxy_adapter/dispatchers/json_rpc_dispatcher.py,sha256=Ibp-4d4kKf_AW_yv_27Zb_PgFIfa3tZLmuppMg8YqsY,8034
|
5
|
+
mcp_proxy_adapter/examples/analyze_config.py,sha256=vog7TNHDw5ZoYhQLbAvZvEoufmQwH54KJzQBJrSq5w4,4283
|
6
|
+
mcp_proxy_adapter/examples/basic_integration.py,sha256=mtRval4VSUgTb_C2p8U_DPPSEKA08dZYKZk-bOrE4H4,4470
|
7
|
+
mcp_proxy_adapter/examples/docstring_and_schema_example.py,sha256=c96L4KF_7yWzffmvd4hyeQuXSdYyYkv7Uvuy0QxgMcQ,1929
|
8
|
+
mcp_proxy_adapter/examples/extension_example.py,sha256=vnatnFdNTapMpPcQ79Ugitk92ZiUfpLTs7Dvsodf1og,2277
|
9
|
+
mcp_proxy_adapter/examples/help_best_practices.py,sha256=Bit9Ywl9vGvM_kuV8DJ6pIDK4mY4mF2Gia9rLc56RpI,2646
|
10
|
+
mcp_proxy_adapter/examples/help_usage.py,sha256=JIUsZofdLFyI7FcwPF-rLxipF1-HaZINzVK1KBh0vxA,2577
|
11
|
+
mcp_proxy_adapter/examples/mcp_proxy_client.py,sha256=z4IzFlGigVTQSb8TpcrQ_a0migsmC58LnNwc8wZmTfw,3811
|
12
|
+
mcp_proxy_adapter/examples/openapi_server.py,sha256=5gRM-EHvMsnNtS_M6l_pNPN5EkSf4X1Lcq4E1Xs5tp0,13387
|
13
|
+
mcp_proxy_adapter/examples/project_structure_example.py,sha256=sswTo6FZb1F5juHa0FYG3cgvrh3wfgGfJu2bBy5tCm4,1460
|
14
|
+
mcp_proxy_adapter/examples/testing_example.py,sha256=AB13c4C1bjs1145O-yriwyreeVXtMOlQLzs2BCGmprk,1719
|
15
|
+
mcp_proxy_adapter-2.1.13.dist-info/licenses/LICENSE,sha256=OkApFEwdgMCt_mbvUI-eIwKMSTe38K3XnU2DT5ub-wI,1072
|
16
|
+
mcp_proxy_adapter-2.1.13.dist-info/METADATA,sha256=1flBa3KLq1QMMf9T-NcpgpP3LxJY4_BwMAUW21K915Q,8886
|
17
|
+
mcp_proxy_adapter-2.1.13.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
|
18
|
+
mcp_proxy_adapter-2.1.13.dist-info/top_level.txt,sha256=JZT7vPLBYrtroX-ij68JBhJYbjDdghcV-DFySRy-Nnw,18
|
19
|
+
mcp_proxy_adapter-2.1.13.dist-info/RECORD,,
|