Skip to main content
Glama
olibuijr

Iceland News MCP Server

by olibuijr

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
NameRequiredDescriptionDefault
queryYesThe search query - keyword or phrase to find in news articles
sourcesNoSpecific sources to search (e.g., ['ruv', 'mbl']). If not specified, searches all sources.
limitNoMaximum number of results to return (1-100)
caseSensitiveNoWhether 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,
        };
      }
  • 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),
    });
  • 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,
      };
    }
  • 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"),
    });

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/olibuijr/iceland-news-mcp'

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