|
|
@@ -13,6 +13,8 @@ from mcp.server.transport_security import TransportSecuritySettings
|
|
|
|
|
|
from news_mcp.config import DEFAULT_LOOKBACK_HOURS, DEFAULT_TOPICS, DB_PATH
|
|
|
from news_mcp.config import (
|
|
|
+ NEWS_FEED_URL,
|
|
|
+ NEWS_FEED_URLS,
|
|
|
NEWS_PRUNE_INTERVAL_HOURS,
|
|
|
NEWS_PRUNING_ENABLED,
|
|
|
NEWS_REFRESH_INTERVAL_SECONDS,
|
|
|
@@ -102,6 +104,23 @@ def _tool_card(name: str, description: str, inputs: list[dict], outputs: list[st
|
|
|
|
|
|
|
|
|
NEWS_TOOL_CARDS = [
|
|
|
+ _tool_card(
|
|
|
+ "get_feeds",
|
|
|
+ "List all configured RSS feeds with their enabled/disabled status.",
|
|
|
+ [],
|
|
|
+ ["feeds[]: {feed_key, enabled, last_hash, last_item_count, updated_at}"],
|
|
|
+ ["Use this to see which feeds are currently active or disabled."],
|
|
|
+ ),
|
|
|
+ _tool_card(
|
|
|
+ "toggle_feed",
|
|
|
+ "Enable or disable a specific RSS feed by URL.",
|
|
|
+ [
|
|
|
+ {"name": "feed_url", "type": "string", "meaning": "the feed URL to toggle"},
|
|
|
+ {"name": "enabled", "type": "boolean", "meaning": "true to enable, false to disable"},
|
|
|
+ ],
|
|
|
+ ["ok", "feed_key", "enabled"],
|
|
|
+ ["Changes take effect on the next refresh cycle."],
|
|
|
+ ),
|
|
|
_tool_card(
|
|
|
"get_latest_events",
|
|
|
"Get the newest deduplicated clusters for a topic or resolved entity-like query.",
|
|
|
@@ -249,6 +268,34 @@ NEWS_EXAMPLE_CHAINS = [
|
|
|
]
|
|
|
|
|
|
|
|
|
+def _configured_feed_urls() -> list[str]:
|
|
|
+ """Return the configured feed URLs from environment variables."""
|
|
|
+ urls = [u.strip() for u in NEWS_FEED_URLS.split(",") if u.strip()]
|
|
|
+ if not urls:
|
|
|
+ urls = [NEWS_FEED_URL]
|
|
|
+ return urls
|
|
|
+
|
|
|
+
|
|
|
+@mcp.tool(description="List all configured RSS feeds with their current enabled/disabled status.")
|
|
|
+async def get_feeds() -> list[dict]:
|
|
|
+ """Return each feed URL with its enabled flag, last fetch stats, and timestamps."""
|
|
|
+ store = SQLiteClusterStore(DB_PATH)
|
|
|
+ return store.get_feed_state_list()
|
|
|
+
|
|
|
+
|
|
|
+@mcp.tool(description="Enable or disable a specific RSS feed by URL.")
|
|
|
+async def toggle_feed(feed_url: str, enabled: bool) -> dict:
|
|
|
+ """Toggle a feed's active/inactive state.
|
|
|
+
|
|
|
+ Changes take effect on the next background refresh cycle.
|
|
|
+ Returns the updated feed state.
|
|
|
+ """
|
|
|
+ store = SQLiteClusterStore(DB_PATH)
|
|
|
+ store.set_feed_enabled(feed_url.strip(), enabled)
|
|
|
+ updated = store.get_feed_state(feed_url.strip())
|
|
|
+ return {"ok": True, "feed_key": feed_url.strip(), "enabled": enabled, "details": updated}
|
|
|
+
|
|
|
+
|
|
|
@mcp.tool(description="Investigate a topic and return the newest deduplicated news clusters, sorted by recency.")
|
|
|
async def get_latest_events(topic: str | None = None, limit: int = 5, include_articles: bool = False):
|
|
|
limit = max(1, min(int(limit), 20))
|
|
|
@@ -863,6 +910,41 @@ def api_cluster_detail(cluster_id: str):
|
|
|
return _api_err(e, f"detail({cluster_id})")
|
|
|
|
|
|
|
|
|
+# ------------------------------------------------------------------
|
|
|
+# Feed management endpoints (toggle on/off from dashboard)
|
|
|
+# ------------------------------------------------------------------
|
|
|
+
|
|
|
+@app.get("/api/v1/feeds")
|
|
|
+def api_feeds():
|
|
|
+ """List all configured feeds with enabled/disabled status."""
|
|
|
+ try:
|
|
|
+ store = SQLiteClusterStore(DB_PATH)
|
|
|
+ feed_list = store.get_feed_state_list()
|
|
|
+ configured = _configured_feed_urls()
|
|
|
+ return {
|
|
|
+ "feeds": feed_list,
|
|
|
+ "configured_urls": configured,
|
|
|
+ }
|
|
|
+ except Exception as e:
|
|
|
+ return _api_err(e, "feeds")
|
|
|
+
|
|
|
+
|
|
|
+@app.post("/api/v1/feeds/toggle")
|
|
|
+def api_feed_toggle(feed_url: str, enabled: bool):
|
|
|
+ """Toggle a feed's enabled state."""
|
|
|
+ try:
|
|
|
+ store = SQLiteClusterStore(DB_PATH)
|
|
|
+ ok = store.set_feed_enabled(feed_url.strip(), enabled)
|
|
|
+ if not ok:
|
|
|
+ return JSONResponse(
|
|
|
+ status_code=404,
|
|
|
+ content={"error": f"Feed not found: {feed_url}"},
|
|
|
+ )
|
|
|
+ return {"ok": True, "feed_url": feed_url.strip(), "enabled": enabled}
|
|
|
+ except Exception as e:
|
|
|
+ return _api_err(e, f"toggle({feed_url})")
|
|
|
+
|
|
|
+
|
|
|
@app.get("/health")
|
|
|
def health():
|
|
|
return {
|