Files
ai-research-engine/server.py
drjones 34061a4ac1 AI Research Engine — self-hosted knowledge acquisition system
- 10 MCP tools via thin proxy on MacBook
- Backend REST API + Dashboard on CT 145 Docker
- Services: YaCy crawler, OpenSearch index, Qdrant vectors, Ollama LLM
- All 4 services healthy and verified
2026-08-04 06:02:42 -07:00

111 lines
3.6 KiB
Python

#!/usr/bin/env python3
"""
AI Research Engine — Thin MCP Proxy
Runs on MacBook. Forwards all tool calls to the CT 145 backend.
Minimal resource usage — all heavy lifting on Proxmox.
"""
import json
import httpx
from mcp.server import FastMCP
BACKEND_URL = "http://10.30.20.249:8000"
client = httpx.Client(timeout=120.0)
mcp = FastMCP(
"ai-research-engine",
instructions="""
AI Research Engine — private knowledge acquisition system.
search_web(query) — Full-text search across indexed documents
semantic_search(query) — Find documents by meaning (vector search)
crawl_url(url) — Crawl a URL into the index
crawl_topic(topic) — Discover and crawl sources for a topic
research_topic(topic) — Full pipeline: discover → crawl → summarize
retrieve_document(url) — Get full content of an indexed document
summarize_sources(urls, instruction) — AI summary of multiple sources
extract_information(url, schema) — Structured data extraction
create_report(topic, sources) — Generate comprehensive research report
index_status() — System health and stats
""",
)
def _get(path: str) -> dict:
r = client.get(f"{BACKEND_URL}{path}")
r.raise_for_status()
return r.json()
@mcp.tool()
def search_web(query: str, category: str = "", limit: int = 10) -> str:
"""Full-text search across indexed documents. Find by keywords, titles, content."""
r = _get(f"/api/search?q={query}&category={category}&limit={limit}")
return json.dumps(r, indent=2)
@mcp.tool()
def semantic_search(query: str, limit: int = 10) -> str:
"""Search by meaning using vector embeddings. Finds conceptually related docs."""
r = _get(f"/api/semantic-search?q={query}&limit={limit}")
return json.dumps(r, indent=2)
@mcp.tool()
def crawl_url(url: str, depth: int = 1) -> str:
"""Crawl a URL. depth: 0=just this page, 1=+linked pages."""
r = _get(f"/api/crawl?url={url}&depth={depth}")
return json.dumps(r, indent=2)
@mcp.tool()
def crawl_topic(topic: str, max_urls: int = 20) -> str:
"""Discover and crawl sources for a topic using YaCy."""
r = _get(f"/api/crawl-topic?topic={topic}&max_urls={max_urls}")
return json.dumps(r, indent=2)
@mcp.tool()
def research_topic(topic: str) -> str:
"""Full research pipeline: keyword search → semantic search → crawl new sources → AI summary."""
r = _get(f"/api/research?topic={topic}")
return json.dumps(r, indent=2)
@mcp.tool()
def retrieve_document(url: str) -> str:
"""Get full indexed content of a document by URL."""
r = _get(f"/api/document?url={url}")
return json.dumps(r, indent=2)
@mcp.tool()
def summarize_sources(urls: str, instruction: str = "Summarize key points") -> str:
"""Summarize multiple URLs using local LLM. urls: comma-separated."""
r = _get(f"/api/summarize?urls={urls}&instruction={instruction}")
return json.dumps(r, indent=2)
@mcp.tool()
def extract_information(url: str, schema: str = "company names, products, prices, specifications") -> str:
"""Extract structured information from a document using LLM."""
r = _get(f"/api/extract?url={url}&schema={schema}")
return json.dumps(r, indent=2)
@mcp.tool()
def create_report(topic: str, sources: str = "") -> str:
"""Generate a comprehensive research report. sources: optional comma-separated URLs."""
r = _get(f"/api/report?topic={topic}&sources={sources}")
return json.dumps(r, indent=2)
@mcp.tool()
def index_status() -> str:
"""Check health of all backend services: OpenSearch, Qdrant, YaCy, Ollama."""
r = _get("/api/status")
return json.dumps(r, indent=2)
if __name__ == "__main__":
mcp.run(transport="stdio")