Skip to main content
Glama

Infomate MCP

by agudulin
fetch-news.ts5.91 kB
import { z } from "zod"; import { type ToolMetadata, type InferSchema } from "xmcp"; export const schema = {}; export const metadata: ToolMetadata = { name: "fetch-news", description: "Fetch all news from infomate.club/vas3k, collect article descriptions and summarize by feed title", annotations: { title: "Fetch News", readOnlyHint: true, destructiveHint: false, idempotentHint: false, }, }; interface Article { title: string; description: string; url: string; timestamp?: string; } interface FeedSummary { feedTitle: string; articles: Article[]; summary: string; } export default async function fetchNews(_params: InferSchema<typeof schema>) { try { const response = await fetch('https://infomate.club/vas3k/'); const html = await response.text(); // Parse the HTML to extract feed titles and articles const feeds: Map<string, Article[]> = new Map(); // Use regex to extract content since we don't have DOM parsing // Look for feed patterns and article content const feedPattern = /<div[^>]*class="[^"]*feed[^"]*"[^>]*>[\s\S]*?<\/div>/gi; const matches = html.match(feedPattern) || []; // Extract individual articles and their descriptions const articlePattern = /<a[^>]+href="([^"]+)"[^>]*>([^<]+)<\/a>/gi; const descriptionPattern = /<p[^>]*>([^<]+)<\/p>/gi; let currentFeed = "General"; let match; // Simple parsing approach - look for common patterns const lines = html.split('\n'); let articles: Article[] = []; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); // Look for feed indicators (favicon + text patterns) if (line.includes('favicon') || line.includes('Hacker News') || line.includes('Techmeme') || line.includes('GitHub') || line.includes('Reddit') || line.includes('Pinboard')) { // Extract feed name const feedMatch = line.match(/>(.*?)</); if (feedMatch) { if (articles.length > 0) { feeds.set(currentFeed, [...(feeds.get(currentFeed) || []), ...articles]); articles = []; } currentFeed = feedMatch[1].trim() || currentFeed; } } // Look for article links const linkMatch = line.match(/<a[^>]+href="([^"]+)"[^>]*>([^<]+)<\/a>/); if (linkMatch) { const url = linkMatch[1]; const title = linkMatch[2].trim(); // Look for description in following lines let description = ""; for (let j = i + 1; j < Math.min(i + 5, lines.length); j++) { const nextLine = lines[j].trim(); if (nextLine && !nextLine.includes('<a') && !nextLine.includes('href')) { const textMatch = nextLine.match(/>([^<]+)</); if (textMatch) { description = textMatch[1].trim(); break; } } } if (title && url) { articles.push({ title, description: description || title, url: url.startsWith('http') ? url : `https://infomate.club${url}` }); } } } // Add remaining articles if (articles.length > 0) { feeds.set(currentFeed, [...(feeds.get(currentFeed) || []), ...articles]); } // Generate summaries for each feed const summaries: FeedSummary[] = []; for (const [feedTitle, feedArticles] of feeds.entries()) { if (feedArticles.length === 0) continue; // Create a summary based on article titles and descriptions const summary = generateFeedSummary(feedArticles); summaries.push({ feedTitle, articles: feedArticles, summary }); } // Format the response let result = `# Vas3k News Summary\n\n`; result += `Found ${summaries.length} feeds with ${summaries.reduce((total, feed) => total + feed.articles.length, 0)} total articles.\n\n`; for (const feed of summaries) { result += `## ${feed.feedTitle} (${feed.articles.length} articles)\n\n`; result += `**Summary:** ${feed.summary}\n\n`; result += `**Articles:**\n`; for (const article of feed.articles.slice(0, 5)) { // Limit to first 5 articles per feed result += `- [${article.title}](${article.url})\n`; if (article.description && article.description !== article.title) { result += ` ${article.description}\n`; } } if (feed.articles.length > 5) { result += `- ... and ${feed.articles.length - 5} more articles\n`; } result += `\n`; } return { content: [{ type: "text", text: result }], }; } catch (error) { return { content: [{ type: "text", text: `Error fetching news: ${error instanceof Error ? error.message : 'Unknown error'}` }], }; } } function generateFeedSummary(articles: Article[]): string { if (articles.length === 0) return "No articles found."; // Extract key topics from titles and descriptions const topics = new Set<string>(); const keywords = new Map<string, number>(); for (const article of articles) { const text = `${article.title} ${article.description}`.toLowerCase(); // Simple keyword extraction const words = text.match(/\b\w{4,}\b/g) || []; for (const word of words) { if (!['this', 'that', 'with', 'from', 'they', 'have', 'been', 'will', 'said', 'more', 'here', 'also'].includes(word)) { keywords.set(word, (keywords.get(word) || 0) + 1); } } } // Get top keywords const topKeywords = Array.from(keywords.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, 5) .map(([word]) => word); const summary = `This feed contains ${articles.length} articles focusing on: ${topKeywords.join(', ')}. Recent topics include various developments in technology, programming, and industry news.`; return summary; }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/agudulin/infomate-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server