robyn 0.73.0__cp311-cp311-macosx_10_12_x86_64.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.
Potentially problematic release.
This version of robyn might be problematic. Click here for more details.
- robyn/__init__.py +757 -0
- robyn/__main__.py +4 -0
- robyn/ai.py +308 -0
- robyn/argument_parser.py +129 -0
- robyn/authentication.py +96 -0
- robyn/cli.py +136 -0
- robyn/dependency_injection.py +71 -0
- robyn/env_populator.py +35 -0
- robyn/events.py +6 -0
- robyn/exceptions.py +32 -0
- robyn/jsonify.py +13 -0
- robyn/logger.py +80 -0
- robyn/mcp.py +461 -0
- robyn/openapi.py +448 -0
- robyn/processpool.py +226 -0
- robyn/py.typed +0 -0
- robyn/reloader.py +164 -0
- robyn/responses.py +208 -0
- robyn/robyn.cpython-311-darwin.so +0 -0
- robyn/robyn.pyi +421 -0
- robyn/router.py +410 -0
- robyn/scaffold/mongo/Dockerfile +12 -0
- robyn/scaffold/mongo/app.py +43 -0
- robyn/scaffold/mongo/requirements.txt +2 -0
- robyn/scaffold/no-db/Dockerfile +12 -0
- robyn/scaffold/no-db/app.py +12 -0
- robyn/scaffold/no-db/requirements.txt +1 -0
- robyn/scaffold/postgres/Dockerfile +32 -0
- robyn/scaffold/postgres/app.py +31 -0
- robyn/scaffold/postgres/requirements.txt +3 -0
- robyn/scaffold/postgres/supervisord.conf +14 -0
- robyn/scaffold/prisma/Dockerfile +15 -0
- robyn/scaffold/prisma/app.py +32 -0
- robyn/scaffold/prisma/requirements.txt +2 -0
- robyn/scaffold/prisma/schema.prisma +13 -0
- robyn/scaffold/sqlalchemy/Dockerfile +12 -0
- robyn/scaffold/sqlalchemy/__init__.py +0 -0
- robyn/scaffold/sqlalchemy/app.py +13 -0
- robyn/scaffold/sqlalchemy/models.py +21 -0
- robyn/scaffold/sqlalchemy/requirements.txt +2 -0
- robyn/scaffold/sqlite/Dockerfile +12 -0
- robyn/scaffold/sqlite/app.py +22 -0
- robyn/scaffold/sqlite/requirements.txt +1 -0
- robyn/scaffold/sqlmodel/Dockerfile +11 -0
- robyn/scaffold/sqlmodel/app.py +46 -0
- robyn/scaffold/sqlmodel/models.py +10 -0
- robyn/scaffold/sqlmodel/requirements.txt +2 -0
- robyn/status_codes.py +137 -0
- robyn/swagger.html +32 -0
- robyn/templating.py +30 -0
- robyn/types.py +44 -0
- robyn/ws.py +67 -0
- robyn-0.73.0.dist-info/METADATA +32 -0
- robyn-0.73.0.dist-info/RECORD +57 -0
- robyn-0.73.0.dist-info/WHEEL +4 -0
- robyn-0.73.0.dist-info/entry_points.txt +3 -0
- robyn-0.73.0.dist-info/licenses/LICENSE +25 -0
robyn/mcp.py
ADDED
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Model Context Protocol (MCP) implementation for Robyn.
|
|
3
|
+
|
|
4
|
+
This module provides MCP server functionality following the JSON-RPC 2.0 specification.
|
|
5
|
+
It allows Robyn applications to serve as MCP servers, exposing resources, tools, and prompts
|
|
6
|
+
to MCP clients like Claude Desktop or other AI applications.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import inspect
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
import re
|
|
13
|
+
from dataclasses import asdict, dataclass
|
|
14
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _extract_uri_params(uri: str) -> List[str]:
|
|
20
|
+
"""Extract parameter names from URI template like 'echo://{message}'"""
|
|
21
|
+
return re.findall(r"\{(\w+)\}", uri)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _generate_schema_from_function(func: Callable) -> Dict[str, Any]:
|
|
25
|
+
"""Generate JSON schema from function signature"""
|
|
26
|
+
sig = inspect.signature(func)
|
|
27
|
+
properties = {}
|
|
28
|
+
required = []
|
|
29
|
+
|
|
30
|
+
for param_name, param in sig.parameters.items():
|
|
31
|
+
# Skip 'self' parameter
|
|
32
|
+
if param_name == "self":
|
|
33
|
+
continue
|
|
34
|
+
|
|
35
|
+
param_schema = {"type": "string"} # Default to string
|
|
36
|
+
|
|
37
|
+
# Try to infer type from annotation
|
|
38
|
+
if param.annotation != inspect.Parameter.empty:
|
|
39
|
+
if param.annotation is str:
|
|
40
|
+
param_schema["type"] = "string"
|
|
41
|
+
elif param.annotation is int:
|
|
42
|
+
param_schema["type"] = "integer"
|
|
43
|
+
elif param.annotation is float:
|
|
44
|
+
param_schema["type"] = "number"
|
|
45
|
+
elif param.annotation is bool:
|
|
46
|
+
param_schema["type"] = "boolean"
|
|
47
|
+
elif hasattr(param.annotation, "__origin__"):
|
|
48
|
+
# Handle generic types like List, Dict, etc.
|
|
49
|
+
param_schema["type"] = "object"
|
|
50
|
+
|
|
51
|
+
properties[param_name] = param_schema
|
|
52
|
+
|
|
53
|
+
# Add to required if no default value
|
|
54
|
+
if param.default == inspect.Parameter.empty:
|
|
55
|
+
required.append(param_name)
|
|
56
|
+
|
|
57
|
+
return {"type": "object", "properties": properties, "required": required}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _generate_prompt_args_from_function(func: Callable) -> List[Dict[str, Any]]:
|
|
61
|
+
"""Generate prompt arguments from function signature"""
|
|
62
|
+
sig = inspect.signature(func)
|
|
63
|
+
arguments = []
|
|
64
|
+
|
|
65
|
+
for param_name, param in sig.parameters.items():
|
|
66
|
+
if param_name == "self":
|
|
67
|
+
continue
|
|
68
|
+
|
|
69
|
+
arg_def = {"name": param_name, "description": f"Parameter {param_name}", "required": param.default == inspect.Parameter.empty}
|
|
70
|
+
arguments.append(arg_def)
|
|
71
|
+
|
|
72
|
+
return arguments
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class MCPResource:
|
|
77
|
+
"""Represents an MCP resource"""
|
|
78
|
+
|
|
79
|
+
uri: str
|
|
80
|
+
name: str
|
|
81
|
+
description: Optional[str] = None
|
|
82
|
+
mimeType: Optional[str] = None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass
|
|
86
|
+
class MCPTool:
|
|
87
|
+
"""Represents an MCP tool"""
|
|
88
|
+
|
|
89
|
+
name: str
|
|
90
|
+
description: str
|
|
91
|
+
inputSchema: Dict[str, Any]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class MCPPrompt:
|
|
96
|
+
"""Represents an MCP prompt template"""
|
|
97
|
+
|
|
98
|
+
name: str
|
|
99
|
+
description: str
|
|
100
|
+
arguments: Optional[List[Dict[str, Any]]] = None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass
|
|
104
|
+
class MCPMessage:
|
|
105
|
+
"""JSON-RPC 2.0 message structure"""
|
|
106
|
+
|
|
107
|
+
jsonrpc: str = "2.0"
|
|
108
|
+
id: Optional[Union[str, int]] = None
|
|
109
|
+
method: Optional[str] = None
|
|
110
|
+
params: Optional[Dict[str, Any]] = None
|
|
111
|
+
result: Optional[Any] = None
|
|
112
|
+
error: Optional[Dict[str, Any]] = None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class MCPError(Exception):
|
|
116
|
+
"""MCP-specific error"""
|
|
117
|
+
|
|
118
|
+
def __init__(self, code: int, message: str, data: Optional[Any] = None):
|
|
119
|
+
self.code = code
|
|
120
|
+
self.message = message
|
|
121
|
+
self.data = data
|
|
122
|
+
super().__init__(message)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class MCPHandler:
|
|
126
|
+
"""Handles MCP protocol requests and responses"""
|
|
127
|
+
|
|
128
|
+
def __init__(self):
|
|
129
|
+
self.resources: Dict[str, Callable] = {}
|
|
130
|
+
self.tools: Dict[str, Callable] = {}
|
|
131
|
+
self.prompts: Dict[str, Callable] = {}
|
|
132
|
+
self.resource_metadata: Dict[str, MCPResource] = {}
|
|
133
|
+
self.tool_metadata: Dict[str, MCPTool] = {}
|
|
134
|
+
self.prompt_metadata: Dict[str, MCPPrompt] = {}
|
|
135
|
+
self.server_info = {"name": "robyn-mcp-server", "version": "1.0.0"}
|
|
136
|
+
|
|
137
|
+
def register_resource(self, uri: str, name: str, handler: Callable, description: Optional[str] = None, mime_type: Optional[str] = None):
|
|
138
|
+
"""Register a resource handler"""
|
|
139
|
+
self.resources[uri] = handler
|
|
140
|
+
self.resource_metadata[uri] = MCPResource(uri=uri, name=name, description=description, mimeType=mime_type)
|
|
141
|
+
|
|
142
|
+
def register_tool(self, name: str, handler: Callable, description: str, input_schema: Dict[str, Any]):
|
|
143
|
+
"""Register a tool handler"""
|
|
144
|
+
self.tools[name] = handler
|
|
145
|
+
self.tool_metadata[name] = MCPTool(name=name, description=description, inputSchema=input_schema)
|
|
146
|
+
|
|
147
|
+
def register_prompt(self, name: str, handler: Callable, description: str, arguments: Optional[List[Dict[str, Any]]] = None):
|
|
148
|
+
"""Register a prompt handler"""
|
|
149
|
+
self.prompts[name] = handler
|
|
150
|
+
self.prompt_metadata[name] = MCPPrompt(name=name, description=description, arguments=arguments)
|
|
151
|
+
|
|
152
|
+
async def handle_request(self, request_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
153
|
+
"""Handle an MCP JSON-RPC request"""
|
|
154
|
+
try:
|
|
155
|
+
# Parse the request
|
|
156
|
+
method = request_data.get("method")
|
|
157
|
+
params = request_data.get("params", {})
|
|
158
|
+
request_id = request_data.get("id")
|
|
159
|
+
|
|
160
|
+
# Handle different MCP methods
|
|
161
|
+
if method == "initialize":
|
|
162
|
+
result = await self._handle_initialize(params)
|
|
163
|
+
elif method == "resources/list":
|
|
164
|
+
result = await self._handle_list_resources(params)
|
|
165
|
+
elif method == "resources/read":
|
|
166
|
+
result = await self._handle_read_resource(params)
|
|
167
|
+
elif method == "tools/list":
|
|
168
|
+
result = await self._handle_list_tools(params)
|
|
169
|
+
elif method == "tools/call":
|
|
170
|
+
result = await self._handle_call_tool(params)
|
|
171
|
+
elif method == "prompts/list":
|
|
172
|
+
result = await self._handle_list_prompts(params)
|
|
173
|
+
elif method == "prompts/get":
|
|
174
|
+
result = await self._handle_get_prompt(params)
|
|
175
|
+
else:
|
|
176
|
+
raise MCPError(-32601, f"Method not found: {method}")
|
|
177
|
+
|
|
178
|
+
# Return successful response
|
|
179
|
+
return {"jsonrpc": "2.0", "id": request_id, "result": result}
|
|
180
|
+
|
|
181
|
+
except MCPError as e:
|
|
182
|
+
return {"jsonrpc": "2.0", "id": request_data.get("id"), "error": {"code": e.code, "message": e.message, "data": e.data}}
|
|
183
|
+
except Exception as e:
|
|
184
|
+
logger.exception("Error handling MCP request")
|
|
185
|
+
return {"jsonrpc": "2.0", "id": request_data.get("id"), "error": {"code": -32603, "message": "Internal error", "data": str(e)}}
|
|
186
|
+
|
|
187
|
+
async def _handle_initialize(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
188
|
+
"""Handle MCP initialize request"""
|
|
189
|
+
return {
|
|
190
|
+
"protocolVersion": "2024-11-05",
|
|
191
|
+
"capabilities": {"resources": {"subscribe": False, "listChanged": False}, "tools": {}, "prompts": {}},
|
|
192
|
+
"serverInfo": self.server_info,
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async def _handle_list_resources(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
196
|
+
"""Handle resources/list request"""
|
|
197
|
+
resources = []
|
|
198
|
+
for uri, metadata in self.resource_metadata.items():
|
|
199
|
+
resources.append(asdict(metadata))
|
|
200
|
+
return {"resources": resources}
|
|
201
|
+
|
|
202
|
+
def _match_uri_template(self, requested_uri: str) -> Optional[Tuple[str, Dict[str, str]]]:
|
|
203
|
+
"""Match requested URI against registered URI templates"""
|
|
204
|
+
for template_uri in self.resources.keys():
|
|
205
|
+
# Extract parameter names from template
|
|
206
|
+
param_names = _extract_uri_params(template_uri)
|
|
207
|
+
|
|
208
|
+
if not param_names:
|
|
209
|
+
# Exact match for non-templated URIs
|
|
210
|
+
if requested_uri == template_uri:
|
|
211
|
+
return template_uri, {}
|
|
212
|
+
continue
|
|
213
|
+
|
|
214
|
+
# Create regex pattern from template
|
|
215
|
+
pattern = template_uri
|
|
216
|
+
for param_name in param_names:
|
|
217
|
+
# Use (.+) to match any characters including slashes for paths
|
|
218
|
+
# Use ([^/]+) for single segments
|
|
219
|
+
if param_name in ["path", "file_path", "directory"]:
|
|
220
|
+
pattern = pattern.replace(f"{{{param_name}}}", r"(.+)")
|
|
221
|
+
else:
|
|
222
|
+
pattern = pattern.replace(f"{{{param_name}}}", r"([^/]+)")
|
|
223
|
+
|
|
224
|
+
match = re.match(f"^{pattern}$", requested_uri)
|
|
225
|
+
if match:
|
|
226
|
+
# Extract parameter values
|
|
227
|
+
param_values = {}
|
|
228
|
+
for i, param_name in enumerate(param_names):
|
|
229
|
+
param_values[param_name] = match.group(i + 1)
|
|
230
|
+
return template_uri, param_values
|
|
231
|
+
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
async def _handle_read_resource(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
235
|
+
"""Handle resources/read request"""
|
|
236
|
+
uri = params.get("uri")
|
|
237
|
+
if not uri:
|
|
238
|
+
raise MCPError(-32602, "URI is required")
|
|
239
|
+
|
|
240
|
+
# Try to match URI template
|
|
241
|
+
match_result = self._match_uri_template(uri)
|
|
242
|
+
if not match_result:
|
|
243
|
+
raise MCPError(-32602, f"Resource not found: {uri}")
|
|
244
|
+
|
|
245
|
+
template_uri, uri_params = match_result
|
|
246
|
+
handler = self.resources[template_uri]
|
|
247
|
+
|
|
248
|
+
# Call the handler with appropriate parameters based on its signature
|
|
249
|
+
try:
|
|
250
|
+
sig = inspect.signature(handler)
|
|
251
|
+
handler_params = list(sig.parameters.keys())
|
|
252
|
+
|
|
253
|
+
if inspect.iscoroutinefunction(handler):
|
|
254
|
+
if uri_params:
|
|
255
|
+
# Use URI parameters for templated resources
|
|
256
|
+
content = await handler(**uri_params)
|
|
257
|
+
elif handler_params:
|
|
258
|
+
# Handler expects parameters, pass the full params dict
|
|
259
|
+
content = await handler(params)
|
|
260
|
+
else:
|
|
261
|
+
# Handler expects no parameters
|
|
262
|
+
content = await handler()
|
|
263
|
+
else:
|
|
264
|
+
if uri_params:
|
|
265
|
+
# Use URI parameters for templated resources
|
|
266
|
+
content = handler(**uri_params)
|
|
267
|
+
elif handler_params:
|
|
268
|
+
# Handler expects parameters, pass the full params dict
|
|
269
|
+
content = handler(params)
|
|
270
|
+
else:
|
|
271
|
+
# Handler expects no parameters
|
|
272
|
+
content = handler()
|
|
273
|
+
except TypeError as e:
|
|
274
|
+
# Handle parameter mismatch errors
|
|
275
|
+
raise MCPError(-32603, f"Handler parameter error: {str(e)}")
|
|
276
|
+
|
|
277
|
+
# Determine content type
|
|
278
|
+
metadata = self.resource_metadata[template_uri]
|
|
279
|
+
mime_type = metadata.mimeType or "text/plain"
|
|
280
|
+
|
|
281
|
+
return {"contents": [{"uri": uri, "mimeType": mime_type, "text": str(content)}]}
|
|
282
|
+
|
|
283
|
+
async def _handle_list_tools(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
284
|
+
"""Handle tools/list request"""
|
|
285
|
+
tools = []
|
|
286
|
+
for name, metadata in self.tool_metadata.items():
|
|
287
|
+
tools.append(asdict(metadata))
|
|
288
|
+
return {"tools": tools}
|
|
289
|
+
|
|
290
|
+
async def _handle_call_tool(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
291
|
+
"""Handle tools/call request"""
|
|
292
|
+
name = params.get("name")
|
|
293
|
+
arguments = params.get("arguments", {})
|
|
294
|
+
|
|
295
|
+
if not name or name not in self.tools:
|
|
296
|
+
raise MCPError(-32602, f"Tool not found: {name}")
|
|
297
|
+
|
|
298
|
+
handler = self.tools[name]
|
|
299
|
+
|
|
300
|
+
# Call the tool handler
|
|
301
|
+
if inspect.iscoroutinefunction(handler):
|
|
302
|
+
result = await handler(**arguments)
|
|
303
|
+
else:
|
|
304
|
+
result = handler(**arguments)
|
|
305
|
+
|
|
306
|
+
return {"content": [{"type": "text", "text": str(result)}]}
|
|
307
|
+
|
|
308
|
+
async def _handle_list_prompts(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
309
|
+
"""Handle prompts/list request"""
|
|
310
|
+
prompts = []
|
|
311
|
+
for name, metadata in self.prompt_metadata.items():
|
|
312
|
+
prompts.append(asdict(metadata))
|
|
313
|
+
return {"prompts": prompts}
|
|
314
|
+
|
|
315
|
+
async def _handle_get_prompt(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
316
|
+
"""Handle prompts/get request"""
|
|
317
|
+
name = params.get("name")
|
|
318
|
+
arguments = params.get("arguments", {})
|
|
319
|
+
|
|
320
|
+
if not name or name not in self.prompts:
|
|
321
|
+
raise MCPError(-32602, f"Prompt not found: {name}")
|
|
322
|
+
|
|
323
|
+
handler = self.prompts[name]
|
|
324
|
+
|
|
325
|
+
# Call the prompt handler
|
|
326
|
+
if inspect.iscoroutinefunction(handler):
|
|
327
|
+
result = await handler(**arguments)
|
|
328
|
+
else:
|
|
329
|
+
result = handler(**arguments)
|
|
330
|
+
|
|
331
|
+
# Ensure result is in the expected format
|
|
332
|
+
if isinstance(result, str):
|
|
333
|
+
messages = [{"role": "user", "content": {"type": "text", "text": result}}]
|
|
334
|
+
elif isinstance(result, list):
|
|
335
|
+
messages = result
|
|
336
|
+
else:
|
|
337
|
+
messages = [{"role": "user", "content": {"type": "text", "text": str(result)}}]
|
|
338
|
+
|
|
339
|
+
return {"description": self.prompt_metadata[name].description, "messages": messages}
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
class MCPApp:
|
|
343
|
+
"""MCP application wrapper for Robyn"""
|
|
344
|
+
|
|
345
|
+
def __init__(self, robyn_app):
|
|
346
|
+
self.app = robyn_app
|
|
347
|
+
self.handler = MCPHandler()
|
|
348
|
+
self._setup_routes()
|
|
349
|
+
|
|
350
|
+
def _setup_routes(self):
|
|
351
|
+
"""Setup MCP routes on the Robyn app"""
|
|
352
|
+
|
|
353
|
+
@self.app.post("/mcp")
|
|
354
|
+
async def handle_mcp_request(request):
|
|
355
|
+
"""Handle MCP JSON-RPC requests"""
|
|
356
|
+
try:
|
|
357
|
+
# Parse JSON request - try multiple approaches
|
|
358
|
+
try:
|
|
359
|
+
request_data = request.json()
|
|
360
|
+
except (ValueError, TypeError, AttributeError):
|
|
361
|
+
# Fallback to parsing body as string
|
|
362
|
+
body = request.body
|
|
363
|
+
if isinstance(body, str):
|
|
364
|
+
request_data = json.loads(body)
|
|
365
|
+
else:
|
|
366
|
+
request_data = json.loads(body.decode("utf-8"))
|
|
367
|
+
|
|
368
|
+
# Handle case where request.json() returns a string instead of dict
|
|
369
|
+
if isinstance(request_data, str):
|
|
370
|
+
request_data = json.loads(request_data)
|
|
371
|
+
|
|
372
|
+
# Handle the request
|
|
373
|
+
response = await self.handler.handle_request(request_data)
|
|
374
|
+
|
|
375
|
+
return response
|
|
376
|
+
|
|
377
|
+
except json.JSONDecodeError:
|
|
378
|
+
return {"jsonrpc": "2.0", "id": None, "error": {"code": -32700, "message": "Parse error"}}
|
|
379
|
+
except Exception as e:
|
|
380
|
+
logger.exception("Error in MCP request handler")
|
|
381
|
+
return {"jsonrpc": "2.0", "id": None, "error": {"code": -32603, "message": "Internal error", "data": str(e)}}
|
|
382
|
+
|
|
383
|
+
def resource(self, uri: str = None, name: str = None, description: Optional[str] = None, mime_type: Optional[str] = None):
|
|
384
|
+
"""
|
|
385
|
+
Decorator to register an MCP resource
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
uri: Resource URI template (e.g., "echo://{message}")
|
|
389
|
+
name: Human-readable name (auto-generated if not provided)
|
|
390
|
+
description: Resource description (auto-generated if not provided)
|
|
391
|
+
mime_type: MIME type (defaults to "text/plain")
|
|
392
|
+
|
|
393
|
+
Example:
|
|
394
|
+
@app.mcp.resource("echo://{message}")
|
|
395
|
+
def echo_resource(message: str) -> str:
|
|
396
|
+
return f"Resource echo: {message}"
|
|
397
|
+
"""
|
|
398
|
+
|
|
399
|
+
def decorator(func: Callable):
|
|
400
|
+
# Auto-generate metadata if not provided
|
|
401
|
+
actual_uri = uri or f"function://{func.__name__}"
|
|
402
|
+
actual_name = name or func.__name__.replace("_", " ").title()
|
|
403
|
+
actual_description = description or func.__doc__ or f"Resource: {actual_name}"
|
|
404
|
+
actual_mime_type = mime_type or "text/plain"
|
|
405
|
+
|
|
406
|
+
self.handler.register_resource(actual_uri, actual_name, func, actual_description, actual_mime_type)
|
|
407
|
+
return func
|
|
408
|
+
|
|
409
|
+
return decorator
|
|
410
|
+
|
|
411
|
+
def tool(self, name: str = None, description: str = None, input_schema: Dict[str, Any] = None):
|
|
412
|
+
"""
|
|
413
|
+
Decorator to register an MCP tool
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
name: Tool name (defaults to function name)
|
|
417
|
+
description: Tool description (auto-generated if not provided)
|
|
418
|
+
input_schema: JSON schema for inputs (auto-generated if not provided)
|
|
419
|
+
|
|
420
|
+
Example:
|
|
421
|
+
@app.mcp.tool()
|
|
422
|
+
def echo_tool(message: str) -> str:
|
|
423
|
+
return f"Tool echo: {message}"
|
|
424
|
+
"""
|
|
425
|
+
|
|
426
|
+
def decorator(func: Callable):
|
|
427
|
+
# Auto-generate metadata if not provided
|
|
428
|
+
actual_name = name or func.__name__
|
|
429
|
+
actual_description = description or func.__doc__ or f"Tool: {func.__name__}"
|
|
430
|
+
actual_schema = input_schema or _generate_schema_from_function(func)
|
|
431
|
+
|
|
432
|
+
self.handler.register_tool(actual_name, func, actual_description, actual_schema)
|
|
433
|
+
return func
|
|
434
|
+
|
|
435
|
+
return decorator
|
|
436
|
+
|
|
437
|
+
def prompt(self, name: str = None, description: str = None, arguments: Optional[List[Dict[str, Any]]] = None):
|
|
438
|
+
"""
|
|
439
|
+
Decorator to register an MCP prompt
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
name: Prompt name (defaults to function name)
|
|
443
|
+
description: Prompt description (auto-generated if not provided)
|
|
444
|
+
arguments: Prompt arguments (auto-generated if not provided)
|
|
445
|
+
|
|
446
|
+
Example:
|
|
447
|
+
@app.mcp.prompt()
|
|
448
|
+
def echo_prompt(message: str) -> str:
|
|
449
|
+
return f"Please process this message: {message}"
|
|
450
|
+
"""
|
|
451
|
+
|
|
452
|
+
def decorator(func: Callable):
|
|
453
|
+
# Auto-generate metadata if not provided
|
|
454
|
+
actual_name = name or func.__name__
|
|
455
|
+
actual_description = description or func.__doc__ or f"Prompt: {func.__name__}"
|
|
456
|
+
actual_arguments = arguments or _generate_prompt_args_from_function(func)
|
|
457
|
+
|
|
458
|
+
self.handler.register_prompt(actual_name, func, actual_description, actual_arguments)
|
|
459
|
+
return func
|
|
460
|
+
|
|
461
|
+
return decorator
|