Source code for tooluniverse.magicapp_tool
"""MAGICapp guideline list with search.
``GET /api/v1/guidelines`` returns the 15 most recently published public
guidelines unless ``limit`` and ``offset`` are given: 15 of 464 on 2026-10-22,
so ``MAGICapp_list_guidelines`` could never show most of the catalogue and an
agent had no way to find a guideline on its topic. The API has no search
endpoint (``/guidelines/search`` answers 400), so this tool reads the whole
public list (``limit=500`` per page) and filters it here by words in the
guideline's name, organization and description, and by language.
Each guideline carries two ids. ``publishedId`` is the published version, the
one MAGICapp_get_guideline / _get_recommendations / _get_sections take;
``guidelineId`` is the current draft, which answers 401 when the draft is not
public and otherwise returns unpublished content (1336: 413 draft
recommendations against 314 published under 578).
"""
import re
import threading
import time
from typing import Any, Dict, List, Optional
from .base_rest_tool import BaseRESTTool
from .http_utils import request_with_retry
from .tool_registry import register_tool
PAGE_SIZE = 500
MAX_PAGES = 20 # 10,000 guidelines: far above the catalogue, guards a looping API
CATALOGUE_TTL_S = 3600
DEFAULT_LIMIT = 20
MAX_LIMIT = 100
GUIDELINE_URL = "https://app.magicapp.org/#/guideline/{short_code}"
# Fields a guideline is searched by, and the fields each returned row keeps.
SEARCH_FIELDS = ("name", "byName", "owner", "institutionName", "description")
ROW_FIELDS = (
"publishedId",
"guidelineId",
"name",
"byName",
"owner",
"language",
"publishDate",
"publishedRecommendationCount",
"shortCode",
)
_cache: Dict[str, Any] = {"rows": None, "fetched": 0.0}
_cache_lock = threading.Lock()
def _words(text: str) -> List[str]:
return re.findall(r"[a-z0-9]+", str(text or "").lower())
def _holds(tokens: List[str], word: str) -> bool:
"""A query word is held when a token begins with it ('pregnan' -> pregnancy)."""
return any(token.startswith(word) for token in tokens)
def match(rows: List[dict], query: Optional[str]) -> tuple:
"""(matching rows, how): guidelines whose searched fields hold every query
word, those holding them all in the name first; else the guidelines holding
the most query words. Catalogue order (newest first) breaks ties."""
wanted = list(dict.fromkeys(_words(query)))
if not wanted:
return rows, "all"
scored = []
for i, row in enumerate(rows):
tokens = _words(" ".join(str(row.get(f) or "") for f in SEARCH_FIELDS))
held = sum(_holds(tokens, w) for w in wanted)
if held:
title = _words(" ".join(str(row.get(f) or "") for f in ("name", "byName")))
in_title = all(_holds(title, w) for w in wanted)
scored.append((held, in_title, i))
every = [s for s in scored if s[0] == len(wanted)]
if every:
every.sort(key=lambda s: (not s[1], s[2]))
return [rows[i] for _, _, i in every], "every word"
scored.sort(key=lambda s: (-s[0], s[2]))
return [rows[i] for _, _, i in scored], "any word"
def compact(row: dict) -> dict:
out = {key: row.get(key) for key in ROW_FIELDS}
if row.get("shortCode"):
out["url"] = GUIDELINE_URL.format(short_code=row["shortCode"])
return out
[docs]
@register_tool("MAGICappGuidelineListTool")
class MAGICappGuidelineListTool(BaseRESTTool):
"""Search MAGICapp's public guidelines by topic, organization and language."""
[docs]
def catalogue(self) -> List[dict]:
"""Every public guideline, newest first as MAGICapp lists them; kept for
an hour. A failed fetch raises and is not kept."""
with _cache_lock:
if (
_cache["rows"] is not None
and time.time() - _cache["fetched"] < CATALOGUE_TTL_S
):
return _cache["rows"]
url = self.tool_config["fields"]["endpoint"]
rows: List[dict] = []
for page in range(MAX_PAGES):
response = request_with_retry(
self.session,
"GET",
url,
params={"limit": PAGE_SIZE, "offset": page * PAGE_SIZE},
timeout=self.timeout,
max_attempts=3,
)
response.raise_for_status()
batch = response.json()
if not isinstance(batch, list):
raise ValueError(
"MAGICapp returned %s, not a guideline list" % type(batch).__name__
)
rows += batch
if len(batch) < PAGE_SIZE:
break
with _cache_lock:
_cache.update(rows=rows, fetched=time.time())
return rows
[docs]
def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
arguments = arguments or {}
try:
limit = int(arguments.get("limit") or DEFAULT_LIMIT)
offset = int(arguments.get("offset") or 0)
except (TypeError, ValueError):
return {"status": "error", "error": "limit and offset must be integers"}
limit, offset = min(max(limit, 1), MAX_LIMIT), max(offset, 0)
try:
rows = self.catalogue()
except Exception as e: # never raise out of run()
return {
"status": "error",
"error": f"MAGICapp guideline list unavailable: {e}",
}
language = str(arguments.get("language") or "").strip().lower()
pool = (
[
r
for r in rows
if str(r.get("language") or "").lower().startswith(language)
]
if language
else rows
)
found, how = match(pool, arguments.get("query"))
page = found[offset : offset + limit]
metadata = {
"public_guidelines": len(rows),
"matching": len(found),
"returned": len(page),
"offset": offset,
"matched_by": how,
"id_to_use": "publishedId: pass it as guideline_id to MAGICapp_get_recommendations, "
"MAGICapp_get_sections or MAGICapp_get_guideline (guidelineId is the unpublished draft)",
}
if how == "any word":
metadata["matching_note"] = (
"No guideline has every query word in its name, organization or description; "
"these hold some of them, the most first. Check each is on the topic."
)
if offset + len(page) < len(found):
metadata["next_offset"] = offset + len(page)
return {
"status": "success",
"data": [compact(r) for r in page],
"metadata": metadata,
}