search_news
Search Icelandic news articles by keyword or phrase across multiple sources to find relevant information.
Instructions
Search across all Icelandic news sources for articles matching a keyword or phrase. Searches titles and descriptions.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | The search query - keyword or phrase to find in news articles | |
| sources | No | Specific sources to search (e.g., ['ruv', 'mbl']). If not specified, searches all sources. | |
| limit | No | Maximum number of results to return (1-100) | |
| caseSensitive | No | Whether to perform case-sensitive search |
Implementation Reference
- src/index.ts:766-854 (registration)Registration of the 'search_news' MCP tool, defining input/output schemas and the handler function that implements the search logic by querying main news feeds from specified or all sources, matching query in titles/descriptions, sorting by recency, and returning structured results with markdown formatting.
server.registerTool( "search_news", { description: "Search across all Icelandic news sources for articles matching a keyword or phrase. Searches titles and descriptions.", inputSchema: { query: z .string() .describe("The search query - keyword or phrase to find in news articles"), sources: z .array(z.string()) .optional() .describe("Specific sources to search (e.g., ['ruv', 'mbl']). If not specified, searches all sources."), limit: z .number() .min(1) .max(100) .default(20) .describe("Maximum number of results to return (1-100)"), caseSensitive: z .boolean() .default(false) .describe("Whether to perform case-sensitive search"), }, outputSchema: SearchResultSchema, }, async ({ query, sources, limit, caseSensitive }) => { const searchedAt = new Date().toISOString(); const sourcesToSearch = sources && sources.length > 0 ? sources.filter(s => s in SOURCES) as SourceName[] : (Object.keys(SOURCES) as SourceName[]); const allMatches: NewsItem[] = []; const searchQuery = caseSensitive ? query : query.toLowerCase(); // Search through each source's main feed for (const source of sourcesToSearch) { try { // Get main feed (usually 'frettir' or 'fp') const feeds = Object.keys(SOURCES[source].feeds); const mainFeed = feeds.includes('frettir') ? 'frettir' : feeds[0]; const response = await fetchNews(source, mainFeed, 50); for (const item of response.items) { const titleToSearch = caseSensitive ? item.title : item.title.toLowerCase(); const descToSearch = caseSensitive ? item.description : item.description.toLowerCase(); if (titleToSearch.includes(searchQuery) || descToSearch.includes(searchQuery)) { allMatches.push(item); } } } catch { // Skip sources that fail continue; } } // Sort by date (newest first) and limit allMatches.sort((a, b) => new Date(b.pubDate).getTime() - new Date(a.pubDate).getTime()); const results = allMatches.slice(0, limit); const searchResult = { query, searchedAt, sourcesSearched: sourcesToSearch.length, totalMatches: results.length, items: results, }; // Format as markdown let markdown = `# Search Results for "${query}"\n\n`; markdown += `*Searched ${sourcesToSearch.length} sources at ${searchedAt} | Found ${results.length} matches*\n\n`; if (results.length === 0) { markdown += "No articles found matching your search query.\n"; } else { for (const item of results) { markdown += `## ${item.title}\n`; markdown += `**Source:** ${item.sourceName} | **Published:** ${item.pubDate}\n`; markdown += `**Link:** ${item.link}\n\n`; markdown += `${item.description}\n\n---\n\n`; } } return { content: [{ type: "text" as const, text: markdown }], structuredContent: searchResult, }; } - src/index.ts:758-764 (schema)Zod schema defining the structured output format for search_news tool results, including query metadata and array of matching NewsItem objects.
const SearchResultSchema = z.object({ query: z.string(), searchedAt: z.string(), sourcesSearched: z.number(), totalMatches: z.number(), items: z.array(NewsItemSchema), }); - src/index.ts:791-854 (handler)Handler function for search_news tool: fetches recent news from main feeds of sources, performs substring search on titles and descriptions (case-insensitive by default), sorts matches by publication date descending, limits to requested number, generates markdown summary, and returns structured SearchResult.
async ({ query, sources, limit, caseSensitive }) => { const searchedAt = new Date().toISOString(); const sourcesToSearch = sources && sources.length > 0 ? sources.filter(s => s in SOURCES) as SourceName[] : (Object.keys(SOURCES) as SourceName[]); const allMatches: NewsItem[] = []; const searchQuery = caseSensitive ? query : query.toLowerCase(); // Search through each source's main feed for (const source of sourcesToSearch) { try { // Get main feed (usually 'frettir' or 'fp') const feeds = Object.keys(SOURCES[source].feeds); const mainFeed = feeds.includes('frettir') ? 'frettir' : feeds[0]; const response = await fetchNews(source, mainFeed, 50); for (const item of response.items) { const titleToSearch = caseSensitive ? item.title : item.title.toLowerCase(); const descToSearch = caseSensitive ? item.description : item.description.toLowerCase(); if (titleToSearch.includes(searchQuery) || descToSearch.includes(searchQuery)) { allMatches.push(item); } } } catch { // Skip sources that fail continue; } } // Sort by date (newest first) and limit allMatches.sort((a, b) => new Date(b.pubDate).getTime() - new Date(a.pubDate).getTime()); const results = allMatches.slice(0, limit); const searchResult = { query, searchedAt, sourcesSearched: sourcesToSearch.length, totalMatches: results.length, items: results, }; // Format as markdown let markdown = `# Search Results for "${query}"\n\n`; markdown += `*Searched ${sourcesToSearch.length} sources at ${searchedAt} | Found ${results.length} matches*\n\n`; if (results.length === 0) { markdown += "No articles found matching your search query.\n"; } else { for (const item of results) { markdown += `## ${item.title}\n`; markdown += `**Source:** ${item.sourceName} | **Published:** ${item.pubDate}\n`; markdown += `**Link:** ${item.link}\n\n`; markdown += `${item.description}\n\n---\n\n`; } } return { content: [{ type: "text" as const, text: markdown }], structuredContent: searchResult, }; } - src/index.ts:215-225 (schema)Base NewsItem schema used in search_news output (array of NewsItem in SearchResultSchema).
const NewsItemSchema = z.object({ title: z.string().describe("News headline"), link: z.string().url().describe("URL to full article"), description: z.string().describe("Article summary or excerpt"), pubDate: z.string().describe("Publication date (ISO 8601)"), creator: z.string().nullable().optional().describe("Author name if available"), source: z.string().describe("Source identifier (ruv, mbl, etc.)"), sourceName: z.string().describe("Human-readable source name"), feed: z.string().describe("Feed identifier within the source"), feedDescription: z.string().describe("Human-readable feed description"), });