guardian_recommend_longreads
Discover personalized Long Read recommendations from The Guardian archives tailored to your interests, context, and topic preferences. Receive up to 10 curated articles based on specified criteria.
Instructions
Get personalized Long Read recommendations based on context and preferences
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| context | No | Context about interests, current conversation, or what you're looking for | |
| count | No | Number of recommendations (default: 3, max: 10) | |
| from_date | No | Earliest publication date to consider (default: 3 months ago) | |
| topic_preference | No | Specific topic or theme preference (e.g., "climate change", "technology", "culture") |
Implementation Reference
- Main handler function that implements the tool logic: validates params, searches Guardian Long Read articles, analyzes user context, scores relevance, ranks top recommendations, and formats a rich response with reading times, topics, and reasons.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; }
- src/types/guardian.ts:181-186 (schema)Zod input validation schema used by the handler to parse and validate tool arguments.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(), });
- src/tools/index.ts:21-39 (registration)Tool registration mapping the 'guardian_recommend_longreads' name to its handler function, imported from './guardian-recommend-longreads.js'.export function registerTools(client: GuardianClient): Record<string, ToolHandler> { return { guardian_search: (args) => guardianSearch(client, args), guardian_get_article: (args) => guardianGetArticle(client, args), guardian_longread: (args) => guardianLongread(client, args), guardian_lookback: (args) => guardianLookback(client, args), guardian_browse_section: (args) => guardianBrowseSection(client, args), guardian_get_sections: (args) => guardianGetSections(client, args), guardian_search_tags: (args) => guardianSearchTags(client, args), guardian_search_by_length: (args) => guardianSearchByLength(client, args), guardian_search_by_author: (args) => guardianSearchByAuthor(client, args), guardian_find_related: (args) => guardianFindRelated(client, args), guardian_get_article_tags: (args) => guardianGetArticleTags(client, args), guardian_content_timeline: (args) => guardianContentTimeline(client, args), guardian_author_profile: (args) => guardianAuthorProfile(client, args), guardian_topic_trends: (args) => guardianTopicTrends(client, args), guardian_top_stories_by_date: (args) => guardianTopStoriesByDate(client, args), guardian_recommend_longreads: (args) => guardianRecommendLongreads(client, args), };
- src/index.ts:515-540 (registration)MCP protocol tool listing with name, description, and JSON schema for input validation in the ListTools response.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")', }, }, }, },
- Helper function to analyze user-provided context and topic preferences, extracting key interests and themes for relevance scoring.function analyzeContext(context: string, topicPreference: string): ContextAnalysis { const analysis: ContextAnalysis = { interests: [], preferredTypes: [], themes: [] }; // Combine context and topic preference for analysis const fullText = `${context} ${topicPreference}`.toLowerCase(); // Extract interests from common topics and keywords const topicKeywords = [ { keywords: ['climate', 'environment', 'global warming', 'sustainability'], topic: 'Environment' }, { keywords: ['politics', 'election', 'government', 'policy'], topic: 'Politics' }, { keywords: ['technology', 'ai', 'artificial intelligence', 'digital', 'tech'], topic: 'Technology' }, { keywords: ['economy', 'business', 'finance', 'market', 'money'], topic: 'Economics' }, { keywords: ['health', 'medicine', 'pandemic', 'covid', 'mental health'], topic: 'Health' }, { keywords: ['culture', 'art', 'music', 'film', 'literature', 'books'], topic: 'Culture' }, { keywords: ['science', 'research', 'discovery', 'study'], topic: 'Science' }, { keywords: ['society', 'social', 'community', 'inequality', 'justice'], topic: 'Society' }, { keywords: ['travel', 'places', 'cities', 'countries', 'explore'], topic: 'Travel' }, { keywords: ['sports', 'football', 'athletics', 'games'], topic: 'Sports' }, { keywords: ['food', 'cooking', 'restaurants', 'cuisine'], topic: 'Food' }, { keywords: ['war', 'conflict', 'international', 'world', 'global'], topic: 'World Affairs' }, { keywords: ['personal', 'memoir', 'life', 'experience', 'story'], topic: 'Personal Stories' }, { keywords: ['history', 'historical', 'past', 'archive'], topic: 'History' } ]; for (const topicGroup of topicKeywords) { if (topicGroup.keywords.some(keyword => fullText.includes(keyword))) { analysis.interests.push(topicGroup.topic); } } // Extract content type preferences const typeKeywords = [ { keywords: ['investigation', 'investigative', 'expose'], type: 'investigative' }, { keywords: ['profile', 'biography', 'portrait'], type: 'profile' }, { keywords: ['analysis', 'deep dive', 'explained'], type: 'analysis' }, { keywords: ['narrative', 'story', 'tale'], type: 'narrative' }, { keywords: ['review', 'opinion', 'commentary'], type: 'commentary' } ]; for (const typeGroup of typeKeywords) { if (typeGroup.keywords.some(keyword => fullText.includes(keyword))) { analysis.preferredTypes.push(typeGroup.type); } } // Extract themes const themeKeywords = [ { keywords: ['inspiring', 'hopeful', 'positive'], theme: 'uplifting' }, { keywords: ['serious', 'important', 'critical'], theme: 'serious' }, { keywords: ['fascinating', 'interesting', 'curious'], theme: 'intriguing' }, { keywords: ['new', 'recent', 'current'], theme: 'contemporary' }, { keywords: ['unusual', 'weird', 'strange', 'surprising'], theme: 'unusual' } ]; for (const themeGroup of themeKeywords) { if (themeGroup.keywords.some(keyword => fullText.includes(keyword))) { analysis.themes.push(themeGroup.theme); } } // If no interests detected, add some popular defaults if (analysis.interests.length === 0) { analysis.interests = ['Culture', 'Society', 'World Affairs']; } return analysis; }