guardian_author_profile
Analyze Guardian journalist profiles by examining their published articles to identify writing patterns, topics covered, and publication frequency within specified time periods.
Instructions
Generate comprehensive profile analysis for a Guardian journalist
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| author | Yes | Author/journalist name to analyze | |
| analysis_period | No | Year to analyze (e.g., "2024") or use from_date/to_date | |
| from_date | No | Start date (YYYY-MM-DD) - alternative to analysis_period | |
| to_date | No | End date (YYYY-MM-DD) - alternative to analysis_period |
Implementation Reference
- The core handler function that implements the guardian_author_profile tool. It validates input, determines date range, searches for author articles using GuardianClient, computes statistics (articles, sections, monthly output, word count, tags, headlines), analyzes patterns, and returns a formatted profile report.export async function guardianAuthorProfile(client: GuardianClient, args: any): Promise<string> { const params = AuthorProfileParamsSchema.parse(args); // Handle analysis period - can be year or date range let fromDate: string; let toDate: string; if (params.analysis_period && !params.from_date && !params.to_date) { // Handle year format like "2024" const year = params.analysis_period; if (/^\d{4}$/.test(year)) { fromDate = `${year}-01-01`; toDate = `${year}-12-31`; } else { throw new Error('analysis_period must be a 4-digit year (e.g., "2024")'); } } else if (params.from_date && params.to_date) { fromDate = validateDate(params.from_date) || params.from_date; toDate = validateDate(params.to_date) || params.to_date; } else { // Default to last year const now = new Date(); fromDate = `${now.getFullYear() - 1}-01-01`; toDate = `${now.getFullYear() - 1}-12-31`; } if (!validateDate(fromDate) || !validateDate(toDate)) { throw new Error('Invalid date format. Use YYYY-MM-DD format.'); } let result = `Author Profile: ${params.author} (${fromDate} to ${toDate})\n\n`; // Search for all articles by the author in the period const searchParams: Record<string, any> = { q: `"${params.author}"`, 'from-date': fromDate, 'to-date': toDate, 'page-size': 200, // Get as many as possible for analysis 'show-fields': 'headline,byline,firstPublicationDate,wordcount,standfirst', 'show-tags': 'keyword,type', 'order-by': 'newest' }; const response = await client.search(searchParams); const allArticles = response.response.results; // Filter to only articles where the author name appears in the byline const authorArticles = allArticles.filter(article => { const byline = article.fields?.byline || ''; return byline.toLowerCase().includes(params.author.toLowerCase()); }); if (authorArticles.length === 0) { return `No articles found for author "${params.author}" in the specified period.`; } const stats: AuthorStats = { totalArticles: authorArticles.length, sectionCoverage: {}, monthlyOutput: {}, averageWordCount: 0, topTags: {}, recentHeadlines: [] }; // Analyze the articles let totalWords = 0; let wordCountArticles = 0; authorArticles.forEach(article => { // Section coverage const section = article.sectionName || 'Unknown'; stats.sectionCoverage[section] = (stats.sectionCoverage[section] || 0) + 1; // Monthly output if (article.fields?.firstPublicationDate) { const date = new Date(article.fields.firstPublicationDate); const monthKey = date.toLocaleDateString('en-US', { month: 'long', year: 'numeric' }); stats.monthlyOutput[monthKey] = (stats.monthlyOutput[monthKey] || 0) + 1; } // Word count analysis if (article.fields?.wordcount && !isNaN(Number(article.fields.wordcount))) { totalWords += Number(article.fields.wordcount); wordCountArticles++; } // Tag analysis if (article.tags) { article.tags.forEach(tag => { if (tag.type === 'keyword') { stats.topTags[tag.webTitle] = (stats.topTags[tag.webTitle] || 0) + 1; } }); } // Recent headlines (first 5) if (stats.recentHeadlines.length < 5) { stats.recentHeadlines.push(article.webTitle || 'Untitled'); } }); stats.averageWordCount = wordCountArticles > 0 ? Math.round(totalWords / wordCountArticles) : 0; // Format the results result += `**Publishing Statistics**\n`; result += `• Total Articles: ${stats.totalArticles}\n`; result += `• Average Word Count: ${stats.averageWordCount} words\n`; const avgPerMonth = (stats.totalArticles / 12).toFixed(1); result += `• Average Output: ${avgPerMonth} articles per month\n\n`; // Section coverage result += `**Section Coverage**\n`; const sortedSections = Object.entries(stats.sectionCoverage) .sort(([,a], [,b]) => b - a) .slice(0, 8); sortedSections.forEach(([section, count]) => { const percentage = ((count / stats.totalArticles) * 100).toFixed(1); result += `• ${section}: ${count} articles (${percentage}%)\n`; }); result += '\n'; // Monthly activity (show top 6 months) result += `**Most Active Months**\n`; const sortedMonths = Object.entries(stats.monthlyOutput) .sort(([,a], [,b]) => b - a) .slice(0, 6); sortedMonths.forEach(([month, count]) => { result += `• ${month}: ${count} articles\n`; }); result += '\n'; // Top topics/tags result += `**Top Topics**\n`; const sortedTags = Object.entries(stats.topTags) .sort(([,a], [,b]) => b - a) .slice(0, 10); sortedTags.forEach(([tag, count]) => { result += `• ${tag}: ${count} articles\n`; }); result += '\n'; // Recent work result += `**Recent Headlines**\n`; stats.recentHeadlines.forEach((headline, index) => { result += `${index + 1}. ${headline}\n`; }); // Writing patterns analysis result += `\n**Writing Patterns**\n`; // Determine writing style based on word count if (stats.averageWordCount < 400) { result += `• Style: Brief news reporting (avg. ${stats.averageWordCount} words)\n`; } else if (stats.averageWordCount < 800) { result += `• Style: Standard journalism (avg. ${stats.averageWordCount} words)\n`; } else { result += `• Style: Long-form analysis (avg. ${stats.averageWordCount} words)\n`; } // Specialization analysis const topSection = sortedSections[0]; if (topSection && (topSection[1] / stats.totalArticles) > 0.5) { result += `• Specialization: ${topSection[0]} specialist (${((topSection[1] / stats.totalArticles) * 100).toFixed(1)}% coverage)\n`; } else { result += `• Specialization: Multi-section correspondent\n`; } return result; }
- src/types/guardian.ts:161-166 (schema)Zod schema used for input validation in the handler. Defines required 'author' and optional date parameters.export const AuthorProfileParamsSchema = z.object({ author: z.string(), analysis_period: z.string().optional(), // Can be year like "2024" or date range from_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(), to_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(), });
- src/tools/index.ts:21-40 (registration)Tool registration in the registerTools function that maps 'guardian_author_profile' to the handler.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:432-457 (registration)MCP protocol tool registration including name, description, and input schema definition in the ListTools response.{ name: 'guardian_author_profile', description: 'Generate comprehensive profile analysis for a Guardian journalist', inputSchema: { type: 'object', properties: { author: { type: 'string', description: 'Author/journalist name to analyze', }, analysis_period: { type: 'string', description: 'Year to analyze (e.g., "2024") or use from_date/to_date', }, from_date: { type: 'string', description: 'Start date (YYYY-MM-DD) - alternative to analysis_period', }, to_date: { type: 'string', description: 'End date (YYYY-MM-DD) - alternative to analysis_period', }, }, required: ['author'], }, },