|
80 | 80 | import json |
81 | 81 | import logging |
82 | 82 | from contextlib import contextmanager |
| 83 | +from datetime import datetime |
83 | 84 | from jsonschema import Draft7Validator, ValidationError as JSONSchemaValidationError |
84 | 85 | from arango.database import StandardDatabase |
85 | 86 | from arango.exceptions import ArangoError |
|
93 | 94 | validate_graph_integrity, |
94 | 95 | calculate_graph_statistics, |
95 | 96 | ) |
96 | | -from .tool_registry import register_tool |
| 97 | +from .tool_registry import register_tool, TOOL_REGISTRY |
97 | 98 | from .tools import ( |
98 | 99 | ARANGO_QUERY, |
99 | 100 | ARANGO_LIST_COLLECTIONS, |
|
129 | 130 | ARANGO_VALIDATE_GRAPH_INTEGRITY, |
130 | 131 | ARANGO_GRAPH_STATISTICS, |
131 | 132 | ARANGO_DATABASE_STATUS, |
| 133 | + # Pattern 1: Progressive Tool Discovery |
| 134 | + ARANGO_SEARCH_TOOLS, |
| 135 | + ARANGO_LIST_TOOLS_BY_CATEGORY, |
| 136 | + # Pattern 2: Context Switching |
| 137 | + ARANGO_SWITCH_CONTEXT, |
| 138 | + ARANGO_GET_ACTIVE_CONTEXT, |
| 139 | + ARANGO_LIST_CONTEXTS, |
| 140 | + # Pattern 3: Tool Unloading |
| 141 | + ARANGO_ADVANCE_WORKFLOW_STAGE, |
| 142 | + ARANGO_GET_TOOL_USAGE_STATS, |
| 143 | + ARANGO_UNLOAD_TOOLS, |
132 | 144 | ) |
133 | 145 |
|
134 | 146 | # Configure logger for handlers |
@@ -238,6 +250,17 @@ def safe_cursor(cursor): |
238 | 250 | ValidateGraphIntegrityArgs, |
239 | 251 | GraphStatisticsArgs, |
240 | 252 | ArangoDatabaseStatusArgs, |
| 253 | + # Pattern 1: Progressive Tool Discovery |
| 254 | + SearchToolsArgs, |
| 255 | + ListToolsByCategoryArgs, |
| 256 | + # Pattern 2: Context Switching |
| 257 | + SwitchContextArgs, |
| 258 | + GetActiveContextArgs, |
| 259 | + ListContextsArgs, |
| 260 | + # Pattern 3: Tool Unloading |
| 261 | + AdvanceWorkflowStageArgs, |
| 262 | + GetToolUsageStatsArgs, |
| 263 | + UnloadToolsArgs, |
241 | 264 | ) |
242 | 265 |
|
243 | 266 |
|
@@ -1791,8 +1814,160 @@ def handle_arango_database_status( |
1791 | 1814 | } |
1792 | 1815 |
|
1793 | 1816 |
|
| 1817 | +# ============================================================================ |
| 1818 | +# MCP Design Pattern Tools |
| 1819 | +# ============================================================================ |
| 1820 | + |
| 1821 | +# Tool category mappings for Progressive Tool Discovery |
| 1822 | +TOOL_CATEGORIES = { |
| 1823 | + "core_data": [ |
| 1824 | + ARANGO_QUERY, ARANGO_LIST_COLLECTIONS, ARANGO_INSERT, |
| 1825 | + ARANGO_UPDATE, ARANGO_REMOVE, ARANGO_CREATE_COLLECTION, ARANGO_BACKUP |
| 1826 | + ], |
| 1827 | + "indexing": [ |
| 1828 | + ARANGO_LIST_INDEXES, ARANGO_CREATE_INDEX, ARANGO_DELETE_INDEX, ARANGO_EXPLAIN_QUERY |
| 1829 | + ], |
| 1830 | + "validation": [ |
| 1831 | + ARANGO_VALIDATE_REFERENCES, ARANGO_INSERT_WITH_VALIDATION, |
| 1832 | + ARANGO_BULK_INSERT, ARANGO_BULK_UPDATE |
| 1833 | + ], |
| 1834 | + "schema": [ARANGO_CREATE_SCHEMA, ARANGO_VALIDATE_DOCUMENT], |
| 1835 | + "query": [ARANGO_QUERY_BUILDER, ARANGO_QUERY_PROFILE], |
| 1836 | + "graph_basic": [ |
| 1837 | + ARANGO_CREATE_GRAPH, ARANGO_LIST_GRAPHS, ARANGO_ADD_VERTEX_COLLECTION, |
| 1838 | + ARANGO_ADD_EDGE_DEFINITION, ARANGO_ADD_EDGE, ARANGO_TRAVERSE, ARANGO_SHORTEST_PATH |
| 1839 | + ], |
| 1840 | + "graph_advanced": [ |
| 1841 | + ARANGO_BACKUP_GRAPH, ARANGO_RESTORE_GRAPH, ARANGO_BACKUP_NAMED_GRAPHS, |
| 1842 | + ARANGO_VALIDATE_GRAPH_INTEGRITY, ARANGO_GRAPH_STATISTICS |
| 1843 | + ], |
| 1844 | + "aliases": [ARANGO_GRAPH_TRAVERSAL, ARANGO_ADD_VERTEX], |
| 1845 | + "health": [ARANGO_DATABASE_STATUS] |
| 1846 | +} |
| 1847 | + |
| 1848 | + |
| 1849 | +# Pattern 1: Progressive Tool Discovery |
| 1850 | +@handle_errors |
| 1851 | +@register_tool( |
| 1852 | + name=ARANGO_SEARCH_TOOLS, |
| 1853 | + description="Search for MCP tools by keywords and categories. Enables progressive tool discovery by returning only relevant tools instead of loading all 34 tools upfront.", |
| 1854 | + model=SearchToolsArgs, |
| 1855 | +) |
| 1856 | +def handle_search_tools( |
| 1857 | + db: StandardDatabase, args: Dict[str, Any] |
| 1858 | +) -> Dict[str, Any]: |
| 1859 | + """Search for tools matching keywords and optional category filters. |
| 1860 | +
|
| 1861 | + This tool enables Progressive Tool Discovery pattern by allowing AI agents |
| 1862 | + to search for relevant tools on-demand rather than loading all tools upfront. |
| 1863 | +
|
| 1864 | + Args: |
| 1865 | + db: ArangoDB database instance (not used, but required for handler signature) |
| 1866 | + args: Validated SearchToolsArgs containing keywords, categories, and detail_level |
| 1867 | +
|
| 1868 | + Returns: |
| 1869 | + Dictionary with matching tools and search metadata |
| 1870 | + """ |
| 1871 | + keywords = [kw.lower() for kw in args["keywords"]] |
| 1872 | + categories_filter = args.get("categories") |
| 1873 | + detail_level = args.get("detail_level", "summary") |
| 1874 | + |
| 1875 | + # Build list of tools to search |
| 1876 | + tools_to_search = [] |
| 1877 | + if categories_filter: |
| 1878 | + for cat in categories_filter: |
| 1879 | + if cat in TOOL_CATEGORIES: |
| 1880 | + tools_to_search.extend(TOOL_CATEGORIES[cat]) |
| 1881 | + else: |
| 1882 | + # Search all tools |
| 1883 | + tools_to_search = list(TOOL_REGISTRY.keys()) |
| 1884 | + |
| 1885 | + # Search for matching tools |
| 1886 | + matches = [] |
| 1887 | + for tool_name in tools_to_search: |
| 1888 | + if tool_name not in TOOL_REGISTRY: |
| 1889 | + continue |
| 1890 | + |
| 1891 | + tool_reg = TOOL_REGISTRY[tool_name] |
| 1892 | + search_text = f"{tool_reg.name} {tool_reg.description}".lower() |
| 1893 | + |
| 1894 | + # Check if any keyword matches |
| 1895 | + if any(keyword in search_text for keyword in keywords): |
| 1896 | + if detail_level == "name": |
| 1897 | + matches.append({"name": tool_reg.name}) |
| 1898 | + elif detail_level == "summary": |
| 1899 | + matches.append({ |
| 1900 | + "name": tool_reg.name, |
| 1901 | + "description": tool_reg.description |
| 1902 | + }) |
| 1903 | + else: # full |
| 1904 | + matches.append({ |
| 1905 | + "name": tool_reg.name, |
| 1906 | + "description": tool_reg.description, |
| 1907 | + "inputSchema": tool_reg.model.model_json_schema() |
| 1908 | + }) |
| 1909 | + |
| 1910 | + return { |
| 1911 | + "matches": matches, |
| 1912 | + "total_matches": len(matches), |
| 1913 | + "keywords": args["keywords"], |
| 1914 | + "categories_searched": categories_filter or "all", |
| 1915 | + "detail_level": detail_level |
| 1916 | + } |
| 1917 | + |
| 1918 | + |
| 1919 | +@handle_errors |
| 1920 | +@register_tool( |
| 1921 | + name=ARANGO_LIST_TOOLS_BY_CATEGORY, |
| 1922 | + description="List all MCP tools organized by category. Useful for understanding tool organization and selecting workflow-specific tool sets.", |
| 1923 | + model=ListToolsByCategoryArgs, |
| 1924 | +) |
| 1925 | +def handle_list_tools_by_category( |
| 1926 | + db: StandardDatabase, args: Dict[str, Any] |
| 1927 | +) -> Dict[str, Any]: |
| 1928 | + """List tools organized by category. |
| 1929 | +
|
| 1930 | + Args: |
| 1931 | + db: ArangoDB database instance (not used, but required for handler signature) |
| 1932 | + args: Validated ListToolsByCategoryArgs with optional category filter |
| 1933 | +
|
| 1934 | + Returns: |
| 1935 | + Dictionary with tools organized by category |
| 1936 | + """ |
| 1937 | + category_filter = args.get("category") |
| 1938 | + |
| 1939 | + if category_filter: |
| 1940 | + # Return single category |
| 1941 | + if category_filter not in TOOL_CATEGORIES: |
| 1942 | + return { |
| 1943 | + "error": f"Unknown category: {category_filter}", |
| 1944 | + "available_categories": list(TOOL_CATEGORIES.keys()) |
| 1945 | + } |
| 1946 | + |
| 1947 | + return { |
| 1948 | + "category": category_filter, |
| 1949 | + "tools": TOOL_CATEGORIES[category_filter], |
| 1950 | + "tool_count": len(TOOL_CATEGORIES[category_filter]) |
| 1951 | + } |
| 1952 | + else: |
| 1953 | + # Return all categories |
| 1954 | + result = { |
| 1955 | + "categories": {}, |
| 1956 | + "total_tools": 0 |
| 1957 | + } |
| 1958 | + |
| 1959 | + for cat_name, tool_list in TOOL_CATEGORIES.items(): |
| 1960 | + result["categories"][cat_name] = { |
| 1961 | + "tools": tool_list, |
| 1962 | + "count": len(tool_list) |
| 1963 | + } |
| 1964 | + result["total_tools"] += len(tool_list) |
| 1965 | + |
| 1966 | + return result |
| 1967 | + |
| 1968 | + |
1794 | 1969 | # Register aliases for backward compatibility |
1795 | | -from .tool_registry import ToolRegistration, TOOL_REGISTRY |
| 1970 | +from .tool_registry import ToolRegistration |
1796 | 1971 |
|
1797 | 1972 | # Alias: ARANGO_GRAPH_TRAVERSAL -> handle_traverse |
1798 | 1973 | TOOL_REGISTRY[ARANGO_GRAPH_TRAVERSAL] = ToolRegistration( |
|
0 commit comments