from __future__ import annotations import os from typing import Any import httpx class MCPRegistry: def __init__(self) -> None: self._tools = { "local_property_rag": { "description": "Searches project, property, and unit metadata from root CRM data.", "transport": "python_local", }, "crm_search": { "description": "Queries lead and interaction state from the root PostgreSQL CRM schema.", "transport": "python_local", }, "external_search": { "description": "Abstract external search slot inspired by Sourik's Brave/DDG tools.", "transport": "adapter_slot", }, } def list_tools(self) -> list[dict[str, Any]]: return [{"name": name, **meta} for name, meta in self._tools.items()] async def execute(self, tool_name: str, query: str, *, crm_pool: Any | None = None) -> dict[str, Any]: if tool_name not in self._tools: raise KeyError(f"Unknown MCP tool '{tool_name}'.") if tool_name == "external_search": return await self._external_search(query) if tool_name == "crm_search": return await self._crm_search(query, crm_pool) if tool_name == "local_property_rag": return await self._local_property_rag(query, crm_pool) return {"tool": tool_name, "query": query, "status": "unsupported"} async def _external_search(self, query: str) -> dict[str, Any]: brave_key = os.getenv("BRAVE_API_KEY", "") if brave_key and not brave_key.startswith("PLACEHOLDER"): async with httpx.AsyncClient(timeout=15.0) as client: response = await client.get( "https://api.search.brave.com/res/v1/web/search", headers={"Accept": "application/json", "X-Subscription-Token": brave_key}, params={"q": query, "count": 5}, ) response.raise_for_status() payload = response.json() results = [ { "title": item.get("title"), "url": item.get("url"), "snippet": item.get("description"), } for item in payload.get("web", {}).get("results", []) ] return {"tool": "external_search", "query": query, "status": "ok", "provider": "brave", "results": results} async with httpx.AsyncClient(timeout=15.0) as client: response = await client.get( "https://api.duckduckgo.com/", params={"q": query, "format": "json", "no_html": 1, "no_redirect": 1}, ) response.raise_for_status() payload = response.json() results: list[dict[str, Any]] = [] abstract = payload.get("AbstractText") if abstract: results.append( { "title": payload.get("Heading") or query, "url": payload.get("AbstractURL"), "snippet": abstract, } ) for topic in payload.get("RelatedTopics", [])[:5]: if isinstance(topic, dict) and topic.get("Text"): results.append( { "title": topic.get("Text", "")[:80], "url": topic.get("FirstURL"), "snippet": topic.get("Text"), } ) return {"tool": "external_search", "query": query, "status": "ok", "provider": "duckduckgo", "results": results} async def _crm_search(self, query: str, crm_pool: Any | None) -> dict[str, Any]: if crm_pool is None: return {"tool": "crm_search", "query": query, "status": "unavailable", "reason": "crm_pool_missing"} async with crm_pool.acquire() as conn: rows = await conn.fetch( """ SELECT id, name, email, phone, source, qualification, score, kanban_status, budget, unit_interest FROM leads WHERE LOWER(name) LIKE $1 OR LOWER(COALESCE(email, '')) LIKE $1 OR LOWER(COALESCE(phone, '')) LIKE $1 OR LOWER(COALESCE(notes, '')) LIKE $1 ORDER BY score DESC, updated_at DESC LIMIT 10 """, f"%{query.lower()}%", ) return { "tool": "crm_search", "query": query, "status": "ok", "results": [dict(row) for row in rows], } async def _local_property_rag(self, query: str, crm_pool: Any | None) -> dict[str, Any]: if crm_pool is None: return {"tool": "local_property_rag", "query": query, "status": "unavailable", "reason": "crm_pool_missing"} async with crm_pool.acquire() as conn: rows = await conn.fetch( """ SELECT id, name, source, budget, unit_interest, metadata FROM leads WHERE LOWER(COALESCE(unit_interest, '')) LIKE $1 OR LOWER(COALESCE(notes, '')) LIKE $1 ORDER BY score DESC, updated_at DESC LIMIT 10 """, f"%{query.lower()}%", ) return { "tool": "local_property_rag", "query": query, "status": "ok", "results": [dict(row) for row in rows], } mcp_registry = MCPRegistry()