feat(agent): cross-char search_items tool + bump timeouts

Adds an MCP tool wrapping the inventory-service /search/items endpoint
with include_all_characters=true, so questions like 'find me a bracelet
with Legendary Acid Ward on any unequipped char' resolve in ONE tool call
instead of looping get_inventory over 60+ chars (which timed out at 120s).

- agent/tools.py: search_items_global wrapper
- agent/mcp_overlord.py: register new tool with detailed schema doc
- agent/claude_wrapper.py: include in --allowed-tools whitelist;
  bump timeout 120s -> 240s
- nginx/overlord.conf: bump /api/agent/ proxy timeout 180s -> 300s
- CLAUDE.md: brief Claude to USE search_items for cross-char searches
This commit is contained in:
Erik 2026-04-25 21:13:26 +02:00
parent d3943e894c
commit 4ae18536be
5 changed files with 82 additions and 8 deletions

View file

@ -297,6 +297,40 @@ async def get_inventory_search(
return resp.json()
async def search_items_global(filters: dict[str, Any]) -> dict[str, Any]:
"""Cross-character item search via the inventory service's /search/items.
Use this INSTEAD of looping per-character when the user asks "find an X
on any of my chars" — one DB query vs. 60+ HTTP roundtrips.
Common filter keys (passed straight through as query params):
include_all_characters: bool (set true to search every char)
character: str (single char) | characters: "A,B,C"
text: str (name/description substring)
has_spell: "Legendary Acid Ward" exact spell name
spell_contains: "Legendary" substring match
legendary_cantrips: "Foo,Bar"
equipment_status: "equipped" | "unequipped"
equipment_slot: int (bitmask: 4=chest, 2048=bracelet, 4096=ring, ...)
slot_names: "Bracelet,Ring"
armor_only / jewelry_only / weapon_only: bool
min_armor / max_armor / min_damage / max_damage: int
...and many more see /search/items endpoint docs.
"""
client = await _http()
# Default to all-character search if caller didn't scope; otherwise the
# endpoint refuses with a 400.
params = dict(filters or {})
if not any(
k in params
for k in ("character", "characters", "include_all_characters")
):
params["include_all_characters"] = True
resp = await client.get("/search/items", params=params)
resp.raise_for_status()
return resp.json()
async def get_combat_stats(character_name: str) -> dict[str, Any]:
"""Lifetime + session combat stats for one character (per-element split,
monster encounters, surge counts)."""