diff --git a/apps/web/public/.well-known/openmcp.json b/apps/web/public/.well-known/openmcp.json index c3a7b46..e93a3f1 100644 --- a/apps/web/public/.well-known/openmcp.json +++ b/apps/web/public/.well-known/openmcp.json @@ -26,6 +26,7 @@ "list_authors", "get_author", "directory_stats", + "trending_topics", "submit_feed", "get_openprofile", "update_openprofile", diff --git a/apps/web/src/lib/llms.js b/apps/web/src/lib/llms.js index 9a7067c..4e33174 100644 --- a/apps/web/src/lib/llms.js +++ b/apps/web/src/lib/llms.js @@ -143,7 +143,7 @@ export async function llmsTxt(opts = {}) { '', 'The same capabilities as the endpoints below, as tools an agent can call:', 'search, list_feeds, get_feed, list_topics, get_topic, topic_posts, read_post,', - 'random_feed, directory_stats and submit_feed. Point an MCP client at', + 'random_feed, directory_stats, trending_topics and submit_feed. Point an MCP client at', `\`${base}/mcp\` — it answers both the current protocol revision and the`, 'older handshake-based ones. Documentation for humans is at the same URL in a', `browser: ${base}/mcp`, diff --git a/apps/web/src/lib/mcp/tools.js b/apps/web/src/lib/mcp/tools.js index f8e25aa..ca352bb 100644 --- a/apps/web/src/lib/mcp/tools.js +++ b/apps/web/src/lib/mcp/tools.js @@ -1,4 +1,4 @@ -import { q, newId, authors as people, webrings } from '@rssamplifier/db'; +import { q, newId, authors as people, trends, webrings } from '@rssamplifier/db'; import { topicSlug } from '@rssamplifier/feed'; import { submitCatalogue, hashIp, EXPRESS_MAX } from '@rssamplifier/ingest'; import { submitFeedTool } from '@profullstack/submit-feed/core'; @@ -652,6 +652,27 @@ export const TOOLS = [ }, }, + { + name: 'trending_topics', + title: 'What the open web is writing about more than last week', + description: + 'Subjects English blogs and news sites used in headlines more than the week before, ranked. `count` is feeds, not posts, in a one in four sample, so it measures movement rather than coverage, and `prior_count` is the same figure for the week before. Updated daily. An empty list means the rollup has not got two days to compare yet.', + inputSchema: { + type: 'object', + properties: { + window: { type: 'integer', minimum: 1, maximum: 14, description: 'Days per side of the comparison. Default 7.' }, + limit: { type: 'integer', minimum: 1, maximum: 200, description: 'Default 50.' }, + }, + }, + annotations: { readOnlyHint: true, openWorldHint: true }, + async run({ window = 7, limit = 50 } = {}) { + const size = Math.min(14, Math.max(1, Math.round(Number(window)) || 7)); + const top = Math.min(200, Math.max(1, Math.round(Number(limit)) || 50)); + const result = await trends.trendingTerms(db(), { window: size, limit: top }); + return { window_days: size, generated_at: result.generatedAt, documents: result.feeds, topics: result.topics }; + }, + }, + { name: 'list_rings', title: 'List the webrings this site hosts',