v2.0.0 — Pure glue architecture: zero copied code, pluggable channels
BREAKING: Complete architectural rewrite.
Before: Copied x-reader's fetcher code into readers/ (1205 lines of borrowed code)
After: Pluggable channel system where each channel is a thin wrapper (~50 lines)
around the best external tool for that platform. Zero copied code.
Architecture:
- channels/base.py — Universal Channel interface (read, search, check)
- channels/web.py — Jina Reader API (swappable)
- channels/github.py — GitHub API (swappable)
- channels/twitter.py — birdx + Jina fallback (swappable)
- channels/youtube.py — yt-dlp (swappable)
- channels/reddit.py — Reddit JSON API + proxy (swappable)
- channels/rss.py — feedparser (swappable)
- channels/bilibili.py — Bilibili API (swappable)
- channels/exa_search.py — Exa semantic search (swappable)
Key design: every backend can be swapped by changing ONE file.
YouTube dies? Change youtube.py. Exa sucks? Swap exa_search.py for Tavily.
Nothing else changes.
Removed: reader.py, schema.py, readers/, search/, utils/ (all x-reader code)
Tests: 36/36 passing
This commit is contained in:
@@ -3,9 +3,8 @@
|
||||
Agent Eyes MCP Server — expose all capabilities as MCP tools.
|
||||
|
||||
Run: python -m agent_eyes.integrations.mcp_server
|
||||
Or: agent-eyes serve (after pip install)
|
||||
|
||||
10 tools for any MCP-compatible AI Agent.
|
||||
8 tools for any MCP-compatible AI Agent.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
@@ -25,7 +24,6 @@ except ImportError:
|
||||
|
||||
|
||||
def create_server():
|
||||
"""Create and configure the MCP server."""
|
||||
if not HAS_MCP:
|
||||
print("MCP not installed. Install: pip install agent-eyes[mcp]", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
@@ -37,97 +35,30 @@ def create_server():
|
||||
@server.list_tools()
|
||||
async def list_tools():
|
||||
return [
|
||||
Tool(
|
||||
name="read_url",
|
||||
description="Read content from any URL. Supports: web pages, GitHub, Reddit, Twitter, YouTube, Bilibili, WeChat, XiaoHongShu, RSS, Telegram.",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"url": {"type": "string", "description": "URL to read"},
|
||||
},
|
||||
"required": ["url"],
|
||||
},
|
||||
),
|
||||
Tool(
|
||||
name="read_batch",
|
||||
description="Read multiple URLs concurrently.",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"urls": {"type": "array", "items": {"type": "string"}, "description": "List of URLs"},
|
||||
},
|
||||
"required": ["urls"],
|
||||
},
|
||||
),
|
||||
Tool(
|
||||
name="detect_platform",
|
||||
description="Detect what platform a URL belongs to (github, reddit, twitter, youtube, etc).",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"url": {"type": "string", "description": "URL to detect"},
|
||||
},
|
||||
"required": ["url"],
|
||||
},
|
||||
),
|
||||
Tool(
|
||||
name="search",
|
||||
description="Semantic web search using Exa. Find any information on the internet.",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"type": "string", "description": "Search query"},
|
||||
"num_results": {"type": "integer", "description": "Number of results (1-10)", "default": 5},
|
||||
},
|
||||
"required": ["query"],
|
||||
},
|
||||
),
|
||||
Tool(
|
||||
name="search_reddit",
|
||||
description="Search Reddit posts and discussions. Works even when Reddit blocks your IP.",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"type": "string", "description": "Search query"},
|
||||
"subreddit": {"type": "string", "description": "Optional subreddit filter (e.g. 'LocalLLaMA')"},
|
||||
"limit": {"type": "integer", "description": "Number of results", "default": 10},
|
||||
},
|
||||
"required": ["query"],
|
||||
},
|
||||
),
|
||||
Tool(
|
||||
name="search_github",
|
||||
description="Search GitHub repositories by topic, keyword, or technology.",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"type": "string", "description": "Search query"},
|
||||
"language": {"type": "string", "description": "Filter by language (e.g. 'python')"},
|
||||
"limit": {"type": "integer", "description": "Number of results", "default": 5},
|
||||
},
|
||||
"required": ["query"],
|
||||
},
|
||||
),
|
||||
Tool(
|
||||
name="search_twitter",
|
||||
description="Search Twitter/X posts. Uses birdx if available, otherwise Exa.",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"type": "string", "description": "Search query"},
|
||||
"limit": {"type": "integer", "description": "Number of results", "default": 10},
|
||||
},
|
||||
"required": ["query"],
|
||||
},
|
||||
),
|
||||
Tool(
|
||||
name="get_status",
|
||||
description="Get Agent Eyes status: which platforms are active, which need configuration.",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
},
|
||||
),
|
||||
Tool(name="read_url",
|
||||
description="Read content from any URL. Supports: web, GitHub, Reddit, Twitter, YouTube, Bilibili, RSS.",
|
||||
inputSchema={"type": "object", "properties": {"url": {"type": "string"}}, "required": ["url"]}),
|
||||
Tool(name="read_batch",
|
||||
description="Read multiple URLs concurrently.",
|
||||
inputSchema={"type": "object", "properties": {"urls": {"type": "array", "items": {"type": "string"}}}, "required": ["urls"]}),
|
||||
Tool(name="detect_platform",
|
||||
description="Detect what platform a URL belongs to.",
|
||||
inputSchema={"type": "object", "properties": {"url": {"type": "string"}}, "required": ["url"]}),
|
||||
Tool(name="search",
|
||||
description="Semantic web search via Exa.",
|
||||
inputSchema={"type": "object", "properties": {"query": {"type": "string"}, "num_results": {"type": "integer", "default": 5}}, "required": ["query"]}),
|
||||
Tool(name="search_reddit",
|
||||
description="Search Reddit posts.",
|
||||
inputSchema={"type": "object", "properties": {"query": {"type": "string"}, "subreddit": {"type": "string"}, "limit": {"type": "integer", "default": 10}}, "required": ["query"]}),
|
||||
Tool(name="search_github",
|
||||
description="Search GitHub repositories.",
|
||||
inputSchema={"type": "object", "properties": {"query": {"type": "string"}, "language": {"type": "string"}, "limit": {"type": "integer", "default": 5}}, "required": ["query"]}),
|
||||
Tool(name="search_twitter",
|
||||
description="Search Twitter/X posts.",
|
||||
inputSchema={"type": "object", "properties": {"query": {"type": "string"}, "limit": {"type": "integer", "default": 10}}, "required": ["query"]}),
|
||||
Tool(name="get_status",
|
||||
description="Get Agent Eyes status: which channels are active.",
|
||||
inputSchema={"type": "object", "properties": {}}),
|
||||
]
|
||||
|
||||
@server.call_tool()
|
||||
@@ -135,53 +66,25 @@ def create_server():
|
||||
try:
|
||||
if name == "read_url":
|
||||
result = await eyes.read(arguments["url"])
|
||||
return [TextContent(type="text", text=json.dumps(result, ensure_ascii=False, indent=2))]
|
||||
|
||||
elif name == "read_batch":
|
||||
results = await eyes.read_batch(arguments["urls"])
|
||||
return [TextContent(type="text", text=json.dumps(results, ensure_ascii=False, indent=2))]
|
||||
|
||||
result = await eyes.read_batch(arguments["urls"])
|
||||
elif name == "detect_platform":
|
||||
platform = eyes.detect_platform(arguments["url"])
|
||||
return [TextContent(type="text", text=f"Platform: {platform}")]
|
||||
|
||||
result = eyes.detect_platform(arguments["url"])
|
||||
elif name == "search":
|
||||
results = await eyes.search(
|
||||
arguments["query"],
|
||||
num_results=arguments.get("num_results", 5),
|
||||
)
|
||||
return [TextContent(type="text", text=json.dumps(results, ensure_ascii=False, indent=2))]
|
||||
|
||||
result = await eyes.search(arguments["query"], arguments.get("num_results", 5))
|
||||
elif name == "search_reddit":
|
||||
results = await eyes.search_reddit(
|
||||
arguments["query"],
|
||||
subreddit=arguments.get("subreddit"),
|
||||
limit=arguments.get("limit", 10),
|
||||
)
|
||||
return [TextContent(type="text", text=json.dumps(results, ensure_ascii=False, indent=2))]
|
||||
|
||||
result = await eyes.search_reddit(arguments["query"], arguments.get("subreddit"), arguments.get("limit", 10))
|
||||
elif name == "search_github":
|
||||
results = await eyes.search_github(
|
||||
arguments["query"],
|
||||
language=arguments.get("language"),
|
||||
limit=arguments.get("limit", 5),
|
||||
)
|
||||
return [TextContent(type="text", text=json.dumps(results, ensure_ascii=False, indent=2))]
|
||||
|
||||
result = await eyes.search_github(arguments["query"], arguments.get("language"), arguments.get("limit", 5))
|
||||
elif name == "search_twitter":
|
||||
results = await eyes.search_twitter(
|
||||
arguments["query"],
|
||||
limit=arguments.get("limit", 10),
|
||||
)
|
||||
return [TextContent(type="text", text=json.dumps(results, ensure_ascii=False, indent=2))]
|
||||
|
||||
result = await eyes.search_twitter(arguments["query"], arguments.get("limit", 10))
|
||||
elif name == "get_status":
|
||||
report = eyes.doctor_report()
|
||||
return [TextContent(type="text", text=report)]
|
||||
|
||||
result = eyes.doctor_report()
|
||||
else:
|
||||
return [TextContent(type="text", text=f"Unknown tool: {name}")]
|
||||
result = f"Unknown tool: {name}"
|
||||
|
||||
text = json.dumps(result, ensure_ascii=False, indent=2) if isinstance(result, (dict, list)) else str(result)
|
||||
return [TextContent(type="text", text=text)]
|
||||
except Exception as e:
|
||||
return [TextContent(type="text", text=f"Error: {str(e)}")]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user