Skip to main content
Glama

guardian_recommend_longreads

Get personalized long-form article recommendations from The Guardian archives based on your interests, topics, and conversation context.

Instructions

Get personalized Long Read recommendations based on context and preferences

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
countNoNumber of recommendations (default: 3, max: 10)
contextNoContext about interests, current conversation, or what you're looking for
from_dateNoEarliest publication date to consider (default: 3 months ago)
topic_preferenceNoSpecific topic or theme preference (e.g., "climate change", "technology", "culture")

Implementation Reference

  • The main asynchronous handler function that executes the tool: parses arguments, fetches Long Read articles from Guardian API, performs context analysis, scores relevance, ranks top recommendations, and returns formatted output.
    export async function guardianRecommendLongreads(client: GuardianClient, args: any): Promise<string> { const params = RecommendLongreadsParamsSchema.parse(args); const count = params.count || 3; const context = params.context || ''; const topicPreference = params.topic_preference || ''; // Determine date range for recommendations let fromDate: string; if (params.from_date) { fromDate = validateDate(params.from_date) || params.from_date; } else { // Default to last 3 months for fresh content const date = new Date(); date.setMonth(date.getMonth() - 3); fromDate = date.toISOString().substring(0, 10); } // Search for Long Read articles const searchParams: Record<string, any> = { 'tag': 'news/series/the-long-read', 'from-date': fromDate, 'page-size': 50, // Get a good selection for analysis 'show-fields': 'headline,standfirst,byline,wordcount,firstPublicationDate,body', 'show-tags': 'keyword,type,contributor', 'order-by': 'newest' }; const response = await client.search(searchParams); const longreads = response.response.results; if (longreads.length === 0) { return `No Long Read articles found since ${fromDate}. Try extending the date range.`; } // Analyze context to extract topics and preferences const contextAnalysis = analyzeContext(context, topicPreference); // Score and rank longreads based on relevance const recommendations = longreads .map(article => scoreLongread(article, contextAnalysis)) .sort((a, b) => b.relevanceScore - a.relevanceScore) .slice(0, count); // Format recommendations let result = `📚 **Curated Long Read Recommendations**\n`; result += `Based on: ${contextAnalysis.interests.length > 0 ? contextAnalysis.interests.join(', ') : 'diverse topics'}\n\n`; recommendations.forEach((rec, index) => { const article = rec.article; const rank = index + 1; result += `**${rank}. ${article.webTitle || 'Untitled'}**\n`; result += `${rec.readingTime} • Relevance: ${rec.relevanceScore.toFixed(1)}/100\n`; if (article.fields) { const { fields } = article; if (fields.byline) { result += `By: ${fields.byline}\n`; } if (fields.firstPublicationDate) { const pubDate = new Date(fields.firstPublicationDate).toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' }); result += `Published: ${pubDate}\n`; } if (fields.standfirst) { result += `Summary: ${fields.standfirst}\n`; } } result += `Topics: ${rec.topics.join(', ')}\n`; result += `Why recommended: ${rec.reasons.join(', ')}\n`; result += `URL: ${article.webUrl || 'N/A'}\n`; result += `Guardian ID: ${article.id || 'N/A'}\n\n`; }); // Add discovery suggestions result += `**Explore More**:\n`; result += `• Use guardian_longread with specific queries for targeted searches\n`; result += `• Try guardian_search_by_author with Long Read contributors\n`; const availableTopics = getPopularLongreadTopics(longreads); if (availableTopics.length > 0) { result += `• Popular Long Read topics: ${availableTopics.slice(0, 5).join(', ')}\n`; } return result; }
  • Zod schema defining the input parameters for the tool, used for validation in the handler.
    export const RecommendLongreadsParamsSchema = z.object({ count: z.number().min(1).max(10).optional(), context: z.string().optional(), from_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(), topic_preference: z.string().optional(), });
  • Registers the tool handler function in the tools map returned by registerTools, mapping the snake_case name to the camelCase function.
    guardian_recommend_longreads: (args) => guardianRecommendLongreads(client, args),
  • src/index.ts:515-540 (registration)
    Registers the tool in the MCP server by including it in the listTools response with name, description, and input schema (mirroring the Zod schema).
    name: 'guardian_recommend_longreads', description: 'Get personalized Long Read recommendations based on context and preferences', inputSchema: { type: 'object', properties: { count: { type: 'integer', description: 'Number of recommendations (default: 3, max: 10)', minimum: 1, maximum: 10, }, context: { type: 'string', description: 'Context about interests, current conversation, or what you\'re looking for', }, from_date: { type: 'string', description: 'Earliest publication date to consider (default: 3 months ago)', }, topic_preference: { type: 'string', description: 'Specific topic or theme preference (e.g., "climate change", "technology", "culture")', }, }, }, },
  • Key helper function that scores individual Long Read articles for relevance to the user's context and preferences.
    function scoreLongread(article: any, contextAnalysis: ContextAnalysis): LongreadRecommendation { let score = 0; const reasons: string[] = []; const topics: string[] = []; // Base quality score for Long Reads score += 40; // All Long Reads have baseline quality // Analyze article tags for topic matching if (article.tags) { const articleTopics = article.tags .filter((tag: any) => tag.type === 'keyword') .map((tag: any) => tag.webTitle); topics.push(...articleTopics.slice(0, 4)); // Limit displayed topics // Score based on interest matching for (const interest of contextAnalysis.interests) { const matchingTags = articleTopics.filter((topic: string) => topic.toLowerCase().includes(interest.toLowerCase()) || interest.toLowerCase().includes(topic.toLowerCase()) ); if (matchingTags.length > 0) { score += 20; reasons.push(`matches ${interest.toLowerCase()}`); } } } // Headline and standfirst analysis const headline = article.webTitle?.toLowerCase() || ''; const standfirst = article.fields?.standfirst?.toLowerCase() || ''; const fullText = `${headline} ${standfirst}`; // Theme matching for (const theme of contextAnalysis.themes) { const themeWords = getThemeWords(theme); if (themeWords.some(word => fullText.includes(word))) { score += 15; reasons.push(`${theme} content`); } } // Content type analysis for (const type of contextAnalysis.preferredTypes) { const typeWords = getTypeWords(type); if (typeWords.some(word => fullText.includes(word))) { score += 18; reasons.push(`${type} style`); } } // Word count analysis const wordCount = article.fields?.wordcount ? parseInt(article.fields.wordcount) : 0; const readingTime = calculateReadingTime(wordCount); if (wordCount > 3000) { score += 10; reasons.push('comprehensive coverage'); } else if (wordCount > 2000) { score += 8; reasons.push('detailed exploration'); } // Recency bonus (fresher content is generally preferred) if (article.fields?.firstPublicationDate) { const pubDate = new Date(article.fields.firstPublicationDate); const daysSince = (Date.now() - pubDate.getTime()) / (1000 * 60 * 60 * 24); if (daysSince < 30) { score += 12; reasons.push('recent publication'); } else if (daysSince < 60) { score += 8; reasons.push('fairly recent'); } } // Author recognition (some Long Read authors are particularly renowned) const byline = article.fields?.byline?.toLowerCase() || ''; const prominentAuthors = ['john', 'rachel', 'david', 'sarah', 'michael', 'emma']; // Simplified check if (prominentAuthors.some(name => byline.includes(name))) { score += 8; reasons.push('acclaimed author'); } // Ensure we have some reasons if (reasons.length === 0) { reasons.push('quality longform journalism'); } // Limit score to 100 score = Math.min(100, score); return { article, relevanceScore: score, reasons, readingTime, topics: topics.length > 0 ? topics : ['General Interest'] }; }

Latest Blog Posts

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/jbenton/guardian-mcp-server'

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