|
2 | 2 | # Licensed under the MIT License. |
3 | 3 | import abc |
4 | 4 | import asyncio |
| 5 | +import inspect |
5 | 6 | import json |
6 | 7 | import logging |
7 | 8 | from abc import ABC |
|
42 | 43 | AssistantQueryInput, AssistantPostInput, InputType, EmbeddingsInput, \ |
43 | 44 | semantic_search_system_prompt, \ |
44 | 45 | SemanticSearchInput, EmbeddingsStoreOutput |
45 | | -from .mcp import MCPToolTrigger |
| 46 | +from .mcp import MCPToolTrigger, MCPToolContext, _TYPE_MAPPING, _extract_type_and_description |
46 | 47 | from .retry_policy import RetryPolicy |
47 | 48 | from .function_name import FunctionName |
48 | 49 | from .warmup import WarmUpTrigger |
@@ -462,6 +463,78 @@ def auth_level(self) -> AuthLevel: |
462 | 463 |
|
463 | 464 | class TriggerApi(DecoratorApi, ABC): |
464 | 465 | """Interface to extend for using existing trigger decorator functions.""" |
| 466 | + def mcp_tool(self) -> Callable[[Callable], Callable]: |
| 467 | + """ |
| 468 | + Decorator to register an MCP tool function. |
| 469 | +
|
| 470 | + Automatically: |
| 471 | + - Infers tool name from function name |
| 472 | + - Extracts first line of docstring as description |
| 473 | + - Extracts parameters and types for tool properties |
| 474 | + - Handles MCPToolContext injection |
| 475 | + """ |
| 476 | + def decorator(target_func: Callable) -> Callable: |
| 477 | + sig = inspect.signature(target_func) |
| 478 | + tool_name = target_func.__name__ |
| 479 | + description = (target_func.__doc__ or "").strip().split("\n")[0] |
| 480 | + |
| 481 | + # Build tool properties metadata |
| 482 | + tool_properties = [] |
| 483 | + for param_name, param in sig.parameters.items(): |
| 484 | + param_type_hint = param.annotation if param.annotation != inspect.Parameter.empty else str |
| 485 | + actual_type, param_description = _extract_type_and_description(param_name, param_type_hint) |
| 486 | + if actual_type is MCPToolContext: |
| 487 | + continue |
| 488 | + property_type = _TYPE_MAPPING.get(actual_type, "string") |
| 489 | + tool_properties.append({ |
| 490 | + "propertyName": param_name, |
| 491 | + "propertyType": property_type, |
| 492 | + "description": param_description, |
| 493 | + }) |
| 494 | + |
| 495 | + tool_properties_json = json.dumps(tool_properties) |
| 496 | + |
| 497 | + # Wrapper function for MCP trigger |
| 498 | + def wrapper(context: str) -> str: |
| 499 | + try: |
| 500 | + content = json.loads(context) |
| 501 | + arguments = content.get("arguments", {}) |
| 502 | + kwargs = {} |
| 503 | + |
| 504 | + for param_name, param in sig.parameters.items(): |
| 505 | + param_type_hint = param.annotation if param.annotation != inspect.Parameter.empty else str |
| 506 | + actual_type, _ = _extract_type_and_description(param_name, param_type_hint) |
| 507 | + |
| 508 | + if actual_type is MCPToolContext: |
| 509 | + kwargs[param_name] = content |
| 510 | + elif param_name in arguments: |
| 511 | + kwargs[param_name] = arguments[param_name] |
| 512 | + else: |
| 513 | + return f"Error: Missing required parameter '{param_name}' for '{tool_name}'" |
| 514 | + |
| 515 | + result = target_func(**kwargs) |
| 516 | + return str(result) |
| 517 | + |
| 518 | + except Exception as e: |
| 519 | + return f"Error executing function '{tool_name}': {str(e)}" |
| 520 | + |
| 521 | + wrapper.__name__ = target_func.__name__ |
| 522 | + wrapper.__doc__ = target_func.__doc__ |
| 523 | + |
| 524 | + # Use the existing FunctionRegister mechanism to add the trigger |
| 525 | + fb = self._configure_function_builder(lambda fb: fb)(wrapper) |
| 526 | + fb.add_trigger( |
| 527 | + trigger=MCPToolTrigger( |
| 528 | + name="context", |
| 529 | + tool_name=tool_name, |
| 530 | + description=description, |
| 531 | + tool_properties=tool_properties_json |
| 532 | + ) |
| 533 | + ) |
| 534 | + |
| 535 | + return fb |
| 536 | + |
| 537 | + return decorator |
465 | 538 |
|
466 | 539 | def route(self, |
467 | 540 | route: Optional[str] = None, |
|
0 commit comments