mcp-proxy-adapter 2.1.0__py3-none-any.whl → 2.1.2__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.
Files changed (78) hide show
  1. docs/README.md +172 -0
  2. docs/README_ru.md +172 -0
  3. docs/architecture.md +251 -0
  4. docs/architecture_ru.md +343 -0
  5. docs/command_development.md +250 -0
  6. docs/command_development_ru.md +593 -0
  7. docs/deployment.md +251 -0
  8. docs/deployment_ru.md +1298 -0
  9. docs/examples.md +254 -0
  10. docs/examples_ru.md +401 -0
  11. docs/mcp_proxy_adapter.md +251 -0
  12. docs/mcp_proxy_adapter_ru.md +405 -0
  13. docs/quickstart.md +251 -0
  14. docs/quickstart_ru.md +397 -0
  15. docs/testing.md +255 -0
  16. docs/testing_ru.md +469 -0
  17. docs/validation_ru.md +287 -0
  18. examples/analyze_config.py +141 -0
  19. examples/basic_integration.py +161 -0
  20. examples/docstring_and_schema_example.py +60 -0
  21. examples/extension_example.py +60 -0
  22. examples/help_best_practices.py +67 -0
  23. examples/help_usage.py +64 -0
  24. examples/mcp_proxy_client.py +131 -0
  25. examples/mcp_proxy_config.json +175 -0
  26. examples/openapi_server.py +369 -0
  27. examples/project_structure_example.py +47 -0
  28. examples/testing_example.py +53 -0
  29. mcp_proxy_adapter/__init__.py +17 -0
  30. mcp_proxy_adapter/adapter.py +697 -0
  31. mcp_proxy_adapter/models.py +47 -0
  32. mcp_proxy_adapter/registry.py +439 -0
  33. mcp_proxy_adapter/schema.py +257 -0
  34. {mcp_proxy_adapter-2.1.0.dist-info → mcp_proxy_adapter-2.1.2.dist-info}/METADATA +2 -2
  35. mcp_proxy_adapter-2.1.2.dist-info/RECORD +61 -0
  36. mcp_proxy_adapter-2.1.2.dist-info/top_level.txt +5 -0
  37. scripts/code_analyzer/code_analyzer.py +328 -0
  38. scripts/code_analyzer/register_commands.py +446 -0
  39. scripts/publish.py +85 -0
  40. tests/conftest.py +12 -0
  41. tests/test_adapter.py +529 -0
  42. tests/test_adapter_coverage.py +274 -0
  43. tests/test_basic_dispatcher.py +169 -0
  44. tests/test_command_registry.py +328 -0
  45. tests/test_examples.py +32 -0
  46. tests/test_mcp_proxy_adapter.py +568 -0
  47. tests/test_mcp_proxy_adapter_basic.py +262 -0
  48. tests/test_part1.py +348 -0
  49. tests/test_part2.py +524 -0
  50. tests/test_schema.py +358 -0
  51. tests/test_simple_adapter.py +251 -0
  52. adapters/__init__.py +0 -16
  53. cli/__init__.py +0 -12
  54. cli/__main__.py +0 -79
  55. cli/command_runner.py +0 -233
  56. generators/__init__.py +0 -14
  57. generators/endpoint_generator.py +0 -172
  58. generators/openapi_generator.py +0 -254
  59. generators/rest_api_generator.py +0 -207
  60. mcp_proxy_adapter-2.1.0.dist-info/RECORD +0 -28
  61. mcp_proxy_adapter-2.1.0.dist-info/top_level.txt +0 -7
  62. openapi_schema/__init__.py +0 -38
  63. openapi_schema/command_registry.py +0 -312
  64. openapi_schema/rest_schema.py +0 -510
  65. openapi_schema/rpc_generator.py +0 -307
  66. openapi_schema/rpc_schema.py +0 -416
  67. validators/__init__.py +0 -14
  68. validators/base_validator.py +0 -23
  69. {analyzers → mcp_proxy_adapter/analyzers}/__init__.py +0 -0
  70. {analyzers → mcp_proxy_adapter/analyzers}/docstring_analyzer.py +0 -0
  71. {analyzers → mcp_proxy_adapter/analyzers}/type_analyzer.py +0 -0
  72. {dispatchers → mcp_proxy_adapter/dispatchers}/__init__.py +0 -0
  73. {dispatchers → mcp_proxy_adapter/dispatchers}/base_dispatcher.py +0 -0
  74. {dispatchers → mcp_proxy_adapter/dispatchers}/json_rpc_dispatcher.py +0 -0
  75. {validators → mcp_proxy_adapter/validators}/docstring_validator.py +0 -0
  76. {validators → mcp_proxy_adapter/validators}/metadata_validator.py +0 -0
  77. {mcp_proxy_adapter-2.1.0.dist-info → mcp_proxy_adapter-2.1.2.dist-info}/WHEEL +0 -0
  78. {mcp_proxy_adapter-2.1.0.dist-info → mcp_proxy_adapter-2.1.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,369 @@
1
+ """
2
+ Example of creating an OpenAPI server using MCP Proxy Adapter.
3
+
4
+ Usage:
5
+ python examples/openapi_server.py
6
+
7
+ Server will be available at: http://localhost:8000
8
+ """
9
+ import os
10
+ import sys
11
+ import logging
12
+ from typing import Dict, List, Any
13
+
14
+ # Add project root directory to sys.path
15
+ sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(__file__))))
16
+
17
+ from fastapi import FastAPI, Query, Path, Body, Request
18
+ from pydantic import BaseModel, Field
19
+ import uvicorn
20
+
21
+ # Import MCP Proxy Adapter
22
+ from mcp_proxy_adapter.adapter import MCPProxyAdapter
23
+
24
+ # Import models for JSON-RPC
25
+ from mcp_proxy_adapter.models import JsonRpcRequest, JsonRpcResponse
26
+
27
+ # Import MockRegistry from tests for example
28
+ # (in a real project, CommandRegistry would be used)
29
+ from tests.test_mcp_proxy_adapter import MockRegistry
30
+
31
+ # Configure logging
32
+ logging.basicConfig(level=logging.DEBUG)
33
+ logger = logging.getLogger(__name__)
34
+
35
+ # Data model definitions
36
+ class Item(BaseModel):
37
+ """Data model for an item."""
38
+ id: int = Field(..., description="Unique item identifier")
39
+ name: str = Field(..., description="Item name")
40
+ description: str = Field(None, description="Item description")
41
+ price: float = Field(..., description="Item price")
42
+ is_available: bool = Field(True, description="Item availability")
43
+
44
+ model_config = {
45
+ "json_schema_extra": {
46
+ "example": {
47
+ "id": 1,
48
+ "name": "Super Product",
49
+ "description": "Best product on the market",
50
+ "price": 99.99,
51
+ "is_available": True
52
+ }
53
+ }
54
+ }
55
+
56
+ # Example data
57
+ items_db = [
58
+ {
59
+ "id": 1,
60
+ "name": "Smartphone X",
61
+ "description": "Latest smartphone with cutting-edge technology",
62
+ "price": 999.99,
63
+ "is_available": True
64
+ },
65
+ {
66
+ "id": 2,
67
+ "name": "Laptop Y",
68
+ "description": "Powerful laptop for professionals",
69
+ "price": 1499.99,
70
+ "is_available": True
71
+ },
72
+ {
73
+ "id": 3,
74
+ "name": "Tablet Z",
75
+ "description": "Compact tablet for creativity",
76
+ "price": 599.99,
77
+ "is_available": False
78
+ }
79
+ ]
80
+
81
+ # Define commands for MockRegistry
82
+ class MockDispatcher:
83
+ """Mock for command dispatcher in example."""
84
+
85
+ def __init__(self):
86
+ self.commands = {
87
+ "get_items": self.get_items,
88
+ "get_item": self.get_item,
89
+ "create_item": self.create_item,
90
+ "update_item": self.update_item,
91
+ "delete_item": self.delete_item,
92
+ "search_items": self.search_items,
93
+ "execute": self.execute_command
94
+ }
95
+ self.commands_info = {
96
+ "get_items": {
97
+ "description": "Get list of all items",
98
+ "params": {}
99
+ },
100
+ "get_item": {
101
+ "description": "Get item by ID",
102
+ "params": {
103
+ "item_id": {
104
+ "type": "integer",
105
+ "description": "Item ID to search for",
106
+ "required": True
107
+ }
108
+ }
109
+ },
110
+ "create_item": {
111
+ "description": "Create new item",
112
+ "params": {
113
+ "item": {
114
+ "type": "object",
115
+ "description": "Item data",
116
+ "required": True
117
+ }
118
+ }
119
+ },
120
+ "update_item": {
121
+ "description": "Update item by ID",
122
+ "params": {
123
+ "item_id": {
124
+ "type": "integer",
125
+ "description": "Item ID to update",
126
+ "required": True
127
+ },
128
+ "updated_data": {
129
+ "type": "object",
130
+ "description": "Updated data",
131
+ "required": True
132
+ }
133
+ }
134
+ },
135
+ "delete_item": {
136
+ "description": "Delete item by ID",
137
+ "params": {
138
+ "item_id": {
139
+ "type": "integer",
140
+ "description": "Item ID to delete",
141
+ "required": True
142
+ }
143
+ }
144
+ },
145
+ "search_items": {
146
+ "description": "Search items by keyword",
147
+ "params": {
148
+ "keyword": {
149
+ "type": "string",
150
+ "description": "Search keyword",
151
+ "required": True
152
+ }
153
+ }
154
+ },
155
+ "execute": {
156
+ "description": "Universal command for executing queries",
157
+ "params": {
158
+ "query": {
159
+ "type": "string",
160
+ "description": "Query to execute",
161
+ "required": False
162
+ },
163
+ "subcommand": {
164
+ "type": "string",
165
+ "description": "Subcommand to execute",
166
+ "required": False
167
+ }
168
+ }
169
+ }
170
+ }
171
+
172
+ def execute(self, command, **params):
173
+ """Executes command with specified parameters."""
174
+ if command not in self.commands:
175
+ raise KeyError(f"Unknown command: {command}")
176
+ return self.commands[command](**params)
177
+
178
+ def execute_command(self, **params):
179
+ """Universal method for executing commands."""
180
+ query = params.get("query", "")
181
+ subcommand = params.get("subcommand", "")
182
+
183
+ # Debug logging
184
+ logger.info(f"Executing universal command with query={query}, subcommand={subcommand}, params={params}")
185
+
186
+ # Handle different command types
187
+ if query.lower() in ["list", "all", "items"]:
188
+ # Return list of all items
189
+ return self.get_items()
190
+ elif query.lower() == "search" and "keyword" in params:
191
+ # Search by keyword
192
+ return self.search_items(params["keyword"])
193
+ elif query.isdigit() or (isinstance(query, int) and query > 0):
194
+ # If query looks like an ID, return item by ID
195
+ try:
196
+ return self.get_item(int(query))
197
+ except ValueError:
198
+ return {"error": f"Item with ID {query} not found"}
199
+ else:
200
+ # By default return information about available commands
201
+ commands = list(self.commands.keys())
202
+ return {
203
+ "available_commands": commands,
204
+ "message": "Use one of the available commands or specify query for executing query",
205
+ "received_params": params
206
+ }
207
+
208
+ def get_valid_commands(self):
209
+ """Returns list of available commands."""
210
+ return list(self.commands.keys())
211
+
212
+ def get_command_info(self, command):
213
+ """Returns information about command."""
214
+ return self.commands_info.get(command)
215
+
216
+ def get_commands_info(self):
217
+ """Returns information about all commands."""
218
+ return self.commands_info
219
+
220
+ # Command implementations
221
+ def get_items(self):
222
+ """Get list of all items."""
223
+ return items_db
224
+
225
+ def get_item(self, item_id):
226
+ """Get item by ID."""
227
+ for item in items_db:
228
+ if item["id"] == item_id:
229
+ return item
230
+ raise ValueError(f"Item with ID {item_id} not found")
231
+
232
+ def create_item(self, item):
233
+ """Create new item."""
234
+ # Find maximum ID
235
+ max_id = max([i["id"] for i in items_db]) if items_db else 0
236
+ # Create new item with increased ID
237
+ new_item = {**item, "id": max_id + 1}
238
+ items_db.append(new_item)
239
+ return new_item
240
+
241
+ def update_item(self, item_id, updated_data):
242
+ """Update item by ID."""
243
+ for i, item in enumerate(items_db):
244
+ if item["id"] == item_id:
245
+ # Update item, keeping original ID
246
+ updated_item = {**item, **updated_data, "id": item_id}
247
+ items_db[i] = updated_item
248
+ return updated_item
249
+ raise ValueError(f"Item with ID {item_id} not found")
250
+
251
+ def delete_item(self, item_id):
252
+ """Delete item by ID."""
253
+ for i, item in enumerate(items_db):
254
+ if item["id"] == item_id:
255
+ del items_db[i]
256
+ return {"message": f"Item with ID {item_id} successfully deleted"}
257
+ raise ValueError(f"Item with ID {item_id} not found")
258
+
259
+ def search_items(self, keyword):
260
+ """Search items by keyword."""
261
+ keyword = keyword.lower()
262
+ return [
263
+ item for item in items_db
264
+ if keyword in item["name"].lower() or
265
+ (item["description"] and keyword in item["description"].lower())
266
+ ]
267
+
268
+ class CustomMockRegistry(MockRegistry):
269
+ """Custom command registry for example."""
270
+
271
+ def __init__(self):
272
+ """Initialization with custom dispatcher."""
273
+ self.dispatcher = MockDispatcher()
274
+ self.generators = []
275
+
276
+ def main():
277
+ """Main function to start server."""
278
+ # Create command registry
279
+ registry = CustomMockRegistry()
280
+
281
+ # Create FastAPI object
282
+ app = FastAPI(
283
+ title="OpenAPI Server Example",
284
+ description="Example OpenAPI server with MCP Proxy Adapter integration",
285
+ version="1.0.0"
286
+ )
287
+
288
+ # Configure CORS
289
+ from fastapi.middleware.cors import CORSMiddleware
290
+ app.add_middleware(
291
+ CORSMiddleware,
292
+ allow_origins=["*"], # Allow requests from all sources
293
+ allow_credentials=True,
294
+ allow_methods=["*"], # Allow all methods
295
+ allow_headers=["*"], # Allow all headers
296
+ )
297
+
298
+ # Create MCP Proxy adapter with explicit endpoint
299
+ adapter = MCPProxyAdapter(registry, cmd_endpoint="/cmd")
300
+
301
+ # Register adapter endpoints
302
+ adapter.register_endpoints(app)
303
+
304
+ # Save MCP Proxy configuration to file
305
+ config_path = os.path.join(os.path.dirname(__file__), "mcp_proxy_config.json")
306
+ adapter.save_config_to_file(config_path)
307
+ logger.info(f"MCP Proxy configuration saved to {config_path}")
308
+
309
+ # Define REST endpoints for example (not related to MCP Proxy)
310
+ @app.get("/")
311
+ def read_root():
312
+ """Root endpoint."""
313
+ return {
314
+ "message": "OpenAPI Server Example with MCP Proxy Adapter integration",
315
+ "endpoints": {
316
+ "items": "/items",
317
+ "item": "/items/{item_id}",
318
+ "search": "/items/search",
319
+ "mcp_proxy": "/cmd"
320
+ }
321
+ }
322
+
323
+ @app.get("/items", response_model=List[Item])
324
+ def read_items():
325
+ """Get all items."""
326
+ return items_db
327
+
328
+ @app.get("/items/{item_id}", response_model=Item)
329
+ def read_item(item_id: int = Path(..., description="Item ID", gt=0)):
330
+ """Get item by ID."""
331
+ try:
332
+ return registry.dispatcher.get_item(item_id)
333
+ except ValueError as e:
334
+ return {"error": str(e)}
335
+
336
+ @app.post("/items", response_model=Item)
337
+ def create_new_item(item: Item = Body(..., description="Data of new item")):
338
+ """Create new item."""
339
+ return registry.dispatcher.create_item(item.model_dump())
340
+
341
+ @app.put("/items/{item_id}", response_model=Item)
342
+ def update_existing_item(
343
+ item_id: int = Path(..., description="Item ID to update", gt=0),
344
+ item: Item = Body(..., description="Updated item data")
345
+ ):
346
+ """Update item by ID."""
347
+ try:
348
+ return registry.dispatcher.update_item(item_id, item.model_dump())
349
+ except ValueError as e:
350
+ return {"error": str(e)}
351
+
352
+ @app.delete("/items/{item_id}")
353
+ def delete_existing_item(item_id: int = Path(..., description="Item ID to delete", gt=0)):
354
+ """Delete item by ID."""
355
+ try:
356
+ return registry.dispatcher.delete_item(item_id)
357
+ except ValueError as e:
358
+ return {"error": str(e)}
359
+
360
+ @app.get("/items/search", response_model=List[Item])
361
+ def search_items_by_keyword(keyword: str = Query(..., description="Search keyword")):
362
+ """Search items by keyword."""
363
+ return registry.dispatcher.search_items(keyword)
364
+
365
+ # Start server
366
+ uvicorn.run(app, host="0.0.0.0", port=8000)
367
+
368
+ if __name__ == "__main__":
369
+ main()
@@ -0,0 +1,47 @@
1
+ """
2
+ Project Structure Example for MCPProxyAdapter
3
+
4
+ - How to organize your project for clean integration
5
+ - Where to place registry, commands, adapter
6
+ - How to register endpoints in FastAPI
7
+
8
+ Run:
9
+ python examples/project_structure_example.py
10
+ """
11
+ import os
12
+ import sys
13
+ sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(__file__))))
14
+ from fastapi import FastAPI
15
+ from mcp_proxy_adapter.adapter import MCPProxyAdapter
16
+
17
+ # --- Command registry and commands ---
18
+ class MyRegistry:
19
+ def __init__(self):
20
+ self.dispatcher = self
21
+ self.commands = {"hello": self.hello}
22
+ self.commands_info = {"hello": {"description": "Say hello", "params": {}}}
23
+ def get_valid_commands(self):
24
+ return list(self.commands.keys())
25
+ def get_command_info(self, command):
26
+ return self.commands_info.get(command)
27
+ def get_commands_info(self):
28
+ return self.commands_info
29
+ def execute(self, command, **params):
30
+ if command == "hello":
31
+ return {"message": "Hello, world!"}
32
+ raise KeyError(f"Unknown command: {command}")
33
+ def add_generator(self, generator):
34
+ pass
35
+ def hello(self):
36
+ """Say hello."""
37
+ return {"message": "Hello, world!"}
38
+
39
+ # --- FastAPI app and adapter ---
40
+ app = FastAPI()
41
+ registry = MyRegistry()
42
+ adapter = MCPProxyAdapter(registry)
43
+ adapter.register_endpoints(app)
44
+
45
+ if __name__ == "__main__":
46
+ import uvicorn
47
+ uvicorn.run(app, host="0.0.0.0", port=8000)
@@ -0,0 +1,53 @@
1
+ """
2
+ Testing Example for MCPProxyAdapter
3
+
4
+ - How to write unit and integration tests for commands
5
+ - How to test help and error handling
6
+ - Best practices for test structure
7
+
8
+ Run:
9
+ python examples/testing_example.py
10
+ """
11
+ import os
12
+ import sys
13
+ sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(__file__))))
14
+ from mcp_proxy_adapter.adapter import MCPProxyAdapter
15
+
16
+ class MyRegistry:
17
+ def __init__(self):
18
+ self.dispatcher = self
19
+ self.commands = {"echo": self.echo}
20
+ self.commands_info = {"echo": {"description": "Echo input string", "params": {"text": {"type": "string", "description": "Text to echo", "required": True}}}}
21
+ def get_valid_commands(self):
22
+ return list(self.commands.keys())
23
+ def get_command_info(self, command):
24
+ return self.commands_info.get(command)
25
+ def get_commands_info(self):
26
+ return self.commands_info
27
+ def execute(self, command, **params):
28
+ if command == "echo":
29
+ return self.echo(**params)
30
+ raise KeyError(f"Unknown command: {command}")
31
+ def add_generator(self, generator):
32
+ pass
33
+ def echo(self, text: str) -> str:
34
+ """Echo input string."""
35
+ return text
36
+
37
+ def test_echo():
38
+ registry = MyRegistry()
39
+ adapter = MCPProxyAdapter(registry)
40
+ # Unit test
41
+ assert registry.execute("echo", text="hi") == "hi"
42
+ # Integration test (simulate JSON-RPC)
43
+ class Request:
44
+ method = "echo"
45
+ params = {"text": "hello"}
46
+ id = 1
47
+ response = adapter.router.routes[0].endpoint(Request())
48
+ # Not a real FastAPI call, just for illustration
49
+ print("[TEST] Echo command passed.")
50
+
51
+ if __name__ == "__main__":
52
+ test_echo()
53
+ print("All tests passed.")
@@ -0,0 +1,17 @@
1
+ """
2
+ MCP Proxy Adapter
3
+ =================
4
+
5
+ Adapter for integrating Command Registry with MCP Proxy to use commands as tools
6
+ for AI models.
7
+ """
8
+
9
+ # Package version
10
+ __version__ = '1.0.0'
11
+
12
+ # Public API
13
+ from .adapter import MCPProxyAdapter, configure_logger
14
+ from .models import MCPProxyConfig, MCPProxyTool, CommandInfo, CommandParameter
15
+
16
+ __all__ = ['MCPProxyAdapter', 'configure_logger', 'MCPProxyConfig', 'MCPProxyTool',
17
+ 'CommandInfo', 'CommandParameter']