brave_news_search
Search for current news articles and breaking news updates using Brave's News Search API to find information on specific topics, events, or trending stories.
Instructions
This tool searches for news articles using Brave's News Search API based on the user's query. Use it when you need current news information, breaking news updates, or articles about specific topics, events, or entities.
When to use:
- Finding recent news articles on specific topics
- Getting breaking news updates
- Researching current events or trending stories
- Gathering news sources and headlines for analysis
Returns a JSON list of news-related results with title, url, and description. Some results may contain snippets of text from the article.
When relaying results in markdown-supporting environments, always cite sources with hyperlinks.
Examples:
- "According to [Reuters](https://www.reuters.com/technology/china-bans/), China bans uncertified and recalled power banks on planes".
- "The [New York Times](https://www.nytimes.com/2025/06/27/us/technology/ev-sales.html) reports that Tesla's EV sales have increased by 20%".
- "According to [BBC News](https://www.bbc.com/news/world-europe-65910000), the UK government has announced a new policy to support renewable energy".
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| count | No | Number of results (1-50, default 20) | |
| country | No | Search query country, where the results come from. The country string is limited to 2 character country codes of supported countries. | US |
| extra_snippets | No | A snippet is an excerpt from a page you get as a result of the query, and extra_snippets allow you to get up to 5 additional, alternative excerpts. Only available under Free AI, Base AI, Pro AI, Base Data, Pro Data and Custom plans. | |
| freshness | No | Filters search results by when they were discovered. The following values are supported: 'pd' - Discovered within the last 24 hours. 'pw' - Discovered within the last 7 Days. 'pm' - Discovered within the last 31 Days. 'py' - Discovered within the last 365 Days. 'YYYY-MM-DDtoYYYY-MM-DD' - Timeframe is also supported by specifying the date range e.g. 2022-04-01to2022-07-30. | pd |
| goggles | No | Goggles act as a custom re-ranking on top of Brave's search index. The parameter supports both a url where the Goggle is hosted or the definition of the Goggle. For more details, refer to the Goggles repository (i.e., https://github.com/brave/goggles-quickstart). | |
| offset | No | Pagination offset (max 9, default 0) | |
| query | Yes | Search query (max 400 chars, 50 words) | |
| safesearch | No | Filters search results for adult content. The following values are supported: 'off' - No filtering. 'moderate' - Filter out explicit content. 'strict' - Filter out explicit and suggestive content. The default value is 'moderate'. | moderate |
| search_lang | No | Search language preference. The 2 or more character language code for which the search results are provided. | en |
| spellcheck | No | Whether to spellcheck provided query. | |
| ui_lang | No | User interface language preferred in response. Usually of the format <language_code>-<country_code>. For more, see RFC 9110. | en-US |
Implementation Reference
- src/tools/news/index.ts:33-53 (handler)The main handler function that executes the tool logic: calls Brave News API via API.issueRequest and formats the results into MCP content blocks with title, description, URL, etc.export const execute = async (params: QueryParams) => { const response = await API.issueRequest<'news'>('news', params); return { content: response.results.map((newsResult) => { return { type: 'text' as const, text: stringify({ url: newsResult.url, title: newsResult.title, age: newsResult.age, page_age: newsResult.page_age, breaking: newsResult.breaking ?? false, description: newsResult.description, extra_snippets: newsResult.extra_snippets, thumbnail: newsResult.thumbnail?.src, }), }; }), }; };
- src/tools/news/params.ts:3-78 (schema)Zod schema defining the input parameters (QueryParams) for the brave_news_search tool.export const params = z.object({ query: z .string() .max(400) .refine((str) => str.split(/\s+/).length <= 50, 'Query cannot exceed 50 words') .describe('Search query (max 400 chars, 50 words)'), country: z .string() .default('US') .describe( 'Search query country, where the results come from. The country string is limited to 2 character country codes of supported countries.' ) .optional(), search_lang: z .string() .default('en') .describe( 'Search language preference. The 2 or more character language code for which the search results are provided.' ) .optional(), ui_lang: z .string() .default('en-US') .describe( 'User interface language preferred in response. Usually of the format <language_code>-<country_code>. For more, see RFC 9110.' ) .optional(), count: z .number() .int() .min(1) .max(50) .default(20) .describe('Number of results (1-50, default 20)') .optional(), offset: z .number() .int() .min(0) .max(9) .default(0) .describe('Pagination offset (max 9, default 0)') .optional(), spellcheck: z .boolean() .default(true) .describe('Whether to spellcheck provided query.') .optional(), safesearch: z .union([z.literal('off'), z.literal('moderate'), z.literal('strict')]) .default('moderate') .describe( "Filters search results for adult content. The following values are supported: 'off' - No filtering. 'moderate' - Filter out explicit content. 'strict' - Filter out explicit and suggestive content. The default value is 'moderate'." ) .optional(), freshness: z .union([z.literal('pd'), z.literal('pw'), z.literal('pm'), z.literal('py'), z.string()]) .default('pd') .describe( "Filters search results by when they were discovered. The following values are supported: 'pd' - Discovered within the last 24 hours. 'pw' - Discovered within the last 7 Days. 'pm' - Discovered within the last 31 Days. 'py' - Discovered within the last 365 Days. 'YYYY-MM-DDtoYYYY-MM-DD' - Timeframe is also supported by specifying the date range e.g. 2022-04-01to2022-07-30." ) .optional(), extra_snippets: z .boolean() .default(false) .describe( 'A snippet is an excerpt from a page you get as a result of the query, and extra_snippets allow you to get up to 5 additional, alternative excerpts. Only available under Free AI, Base AI, Pro AI, Base Data, Pro Data and Custom plans.' ) .optional(), goggles: z .array(z.string()) .describe( "Goggles act as a custom re-ranking on top of Brave's search index. The parameter supports both a url where the Goggle is hosted or the definition of the Goggle. For more details, refer to the Goggles repository (i.e., https://github.com/brave/goggles-quickstart)." ) .optional(), });
- src/tools/news/index.ts:55-66 (registration)Registration function that adds the brave_news_search tool to the MCP server using the name, description, schema, annotations, and execute handler.export const register = (mcpServer: McpServer) => { mcpServer.registerTool( name, { title: name, description: description, inputSchema: params.shape, annotations: annotations, }, execute ); };
- src/BraveAPI/index.ts:32-115 (helper)Core API helper function issueRequest used in the handler to perform the HTTP request to Brave's news search endpoint ('/res/v1/news/search') and return the raw API response.async function issueRequest<T extends keyof Endpoints>( endpoint: T, parameters: Endpoints[T]['params'], // TODO (Sampson): Implement support for custom request headers (helpful for POIs, etc.) requestHeaders: Endpoints[T]['requestHeaders'] = {} as Endpoints[T]['requestHeaders'] ): Promise<Endpoints[T]['response']> { // TODO (Sampson): Improve rate-limit logic to support self-throttling and n-keys // checkRateLimit(); // Determine URL, and setup parameters const url = new URL(`https://api.search.brave.com${typeToPathMap[endpoint]}`); const queryParams = new URLSearchParams(); // TODO (Sampson): Move param-construction/validation to modules for (const [key, value] of Object.entries(parameters)) { // The 'ids' parameter is expected to appear multiple times for multiple IDs if (['localPois', 'localDescriptions'].includes(endpoint)) { if (key === 'ids') { if (Array.isArray(value) && value.length > 0) { value.forEach((id) => queryParams.append(key, id)); } else if (typeof value === 'string') { queryParams.set(key, value); } continue; } } // Handle `result_filter` parameter if (key === 'result_filter') { // Handle special behavior of 'summary' parameter: // Requires `result_filter` to be empty, or only contain 'summarizer' // see: https://bravesoftware.slack.com/archives/C01NNFM9XMM/p1751654841090929 if ('summary' in parameters && parameters.summary === true) { queryParams.set(key, 'summarizer'); } else if (Array.isArray(value) && value.length > 0) { queryParams.set(key, value.join(',')); } continue; } // Handle `goggles` parameter(s) if (key === 'goggles') { if (typeof value === 'string') { queryParams.set(key, value); } else if (Array.isArray(value)) { for (const url of value.filter(isValidGoggleURL)) { queryParams.append(key, url); } } continue; } if (value !== undefined) { queryParams.set(key === 'query' ? 'q' : key, value.toString()); } } // Issue Request const urlWithParams = url.toString() + '?' + queryParams.toString(); const headers = { ...getDefaultRequestHeaders(), ...requestHeaders } as Headers; const response = await fetch(urlWithParams, { headers }); // Handle Error if (!response.ok) { let errorMessage = `${response.status} ${response.statusText}`; try { const responseBody = await response.json(); errorMessage += `\n${stringify(responseBody, true)}`; } catch (error) { errorMessage += `\n${await response.text()}`; } // TODO (Sampson): Setup proper error handling, updating state, etc. throw new Error(errorMessage); } // Return Response const responseBody = await response.json(); return responseBody as Endpoints[T]['response']; }