Source code for sktime_mcp.tools.query_registry

"""
Discovery tools for sktime MCP.
Provides the query_registry tool.
"""

import difflib
import json
from typing import Any

from sktime_mcp.registry.interface import get_registry


[docs] def query_registry_tool( task: str | None = None, tags: dict[str, Any] | str | None = None, query: str | None = None, limit: int = 50, offset: int = 0, ) -> dict[str, Any]: """ Query the sktime registry for estimators,or capability tags. All filters are combined: query narrows by name/docstring, then task and tags are applied on top. To retrieve capability tags instead of estimators, set task="tag" (or "tags"). Args: task: Filter by scitype (e.g., "forecaster", "classifier", "regressor", "transformer", "detector", "metric"). Set to "tag" or "tags" to query available capability tags. tags: Filter estimators by capability tags. Can be a dictionary or a JSON string. Example JSON string: '{"capability:pred_int": true}'. Ignored when task="tag". query: Search by name/description (substring, case-insensitive). limit: Maximum number of results to return (default: 50). Ignored when task="tag". offset: Number of results to skip for pagination (default: 0). Ignored when task="tag". Returns: A dictionary with: - success: bool - results: List of matching estimators or tags - count: Number of results returned in this page - total: Total matching results (before limit/offset) - offset: Current offset (for pagination) - limit: Current limit (for pagination) - has_more: True if more results exist beyond this page """ registry = get_registry() try: # Check pagination bounds if offset < 0: return {"success": False, "error": "offset must be a non-negative integer."} if limit < 1: return {"success": False, "error": "limit must be a positive integer."} # 1. Handle capability tags query if task in ("tag", "tags"): all_tags = registry.get_available_tags() if query: q_lower = query.lower() all_tags = [ t for t in all_tags if q_lower in t.get("tag", "").lower() or q_lower in t.get("description", "").lower() ] total = len(all_tags) page = all_tags[offset : offset + limit] return { "success": True, "results": page, "count": len(page), "total": total, "offset": offset, "limit": limit, "has_more": (offset + limit) < total, "task_filter": task, "query": query, } # 2. Handle estimators query # Validate task if provided if task is not None: valid_tasks = registry.get_available_tasks() if task not in valid_tasks: suggestions = difflib.get_close_matches(task, valid_tasks, n=3, cutoff=0.6) return { "success": False, "error": f"Invalid task: '{task}'. Valid options: {valid_tasks}." + (f" Did you mean: {suggestions}?" if suggestions else ""), } # Validate tag keys if provided if tags is not None: if isinstance(tags, str): try: tags = json.loads(tags) except json.JSONDecodeError as e: return {"success": False, "error": f"Invalid JSON string in 'tags': {e}"} if not isinstance(tags, dict): return {"success": False, "error": "tags must be a dictionary or a JSON string."} valid_tag_keys = {t["tag"] for t in registry.get_available_tags()} invalid_keys = [k for k in tags if k not in valid_tag_keys] if invalid_keys: suggestions = { k: difflib.get_close_matches(k, valid_tag_keys, n=1, cutoff=0.6) for k in invalid_keys } return { "success": False, "error": f"Invalid tag key(s): {invalid_keys}. Use task='tag' to see valid keys.", "suggestions": {k: v[0] if v else None for k, v in suggestions.items()}, } if query: estimators = registry.search_estimators(query) if task: estimators = [e for e in estimators if e.task == task] if tags: estimators = [ e for e in estimators if all(e.tags.get(k) == v for k, v in tags.items()) ] else: estimators = registry.get_all_estimators(task=task, tags=tags) total = len(estimators) page = estimators[offset : offset + limit] results = [est.to_summary() for est in page] return { "success": True, "results": results, "count": len(results), "total": total, "offset": offset, "limit": limit, "has_more": (offset + limit) < total, "task_filter": task, "tag_filter": tags, "query": query, } except Exception as e: return {"success": False, "error": str(e)}