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

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
itemsYes
queryYes
searchedAtYes
totalMatchesYes
sourcesSearchedYes

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"),
    });
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It mentions searching 'titles and descriptions' and 'all Icelandic news sources', but doesn't cover important aspects like rate limits, authentication requirements, pagination behavior, error conditions, or what happens when no results are found. For a search tool with zero annotation coverage, this leaves significant gaps.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is perfectly concise - two clear sentences with zero wasted words. The first sentence establishes the core functionality, and the second adds important scope information about searching titles and descriptions. Every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given that there's an output schema (which means return values are documented elsewhere), the description covers the basic purpose adequately. However, for a search tool with 4 parameters and no annotations, it should ideally provide more context about behavioral aspects like result ordering, freshness, or limitations. The description is minimally complete but lacks depth.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all 4 parameters thoroughly. The description adds minimal value beyond the schema - it mentions 'keyword or phrase' which aligns with the query parameter, but doesn't provide additional context about parameter interactions or search semantics. Baseline 3 is appropriate when the schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Search across all Icelandic news sources for articles matching a keyword or phrase.' It specifies the verb (search), resource (Icelandic news articles), and scope (across all sources). However, it doesn't explicitly differentiate from sibling tools like 'get_news' or 'check_feeds', which prevents a perfect score.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives like 'get_news' or 'check_feeds'. It mentions searching 'titles and descriptions' but doesn't clarify if this is the primary search method or when other tools might be more appropriate. No exclusions or prerequisites are stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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