@brave/brave-search-mcp-server 2.0.80 → 2.0.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,66 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Keep up to date with documentation:
4
+ * https://api-dashboard.search.brave.com/api-reference/summarizer/llm_context/get#responses
5
+ */
6
+ const SnippetsSchema = z
7
+ .array(z.string())
8
+ .describe(`Extracted content chunks. Entries can mix plain prose, markdown-formatted text, and JSON-encoded structured data (e.g., extracted tables, schemas, or page metadata serialized as a string), so consumers should be prepared to detect and parse JSON strings rather than assuming every entry is plain text.`);
9
+ const SourceMetadataSchema = z
10
+ .object({
11
+ title: z.string().optional().describe('The page title for the referenced URL.'),
12
+ hostname: z.string().optional().describe('The hostname extracted from the referenced URL.'),
13
+ age: z
14
+ .array(z.string())
15
+ .optional()
16
+ .describe('Discovery/age information for the source, typically formatted as human-readable, ISO date, and relative-age strings. May be an empty array when unavailable.'),
17
+ site_name: z
18
+ .string()
19
+ .optional()
20
+ .describe('Site name for the source (present when enable_source_metadata is true).'),
21
+ favicon: z
22
+ .string()
23
+ .optional()
24
+ .describe('Favicon URL for the source (present when enable_source_metadata is true).'),
25
+ thumbnail: z
26
+ .object({
27
+ src: z.string().optional().describe('Cached/proxied thumbnail URL served by Brave.'),
28
+ original: z.string().optional().describe('Original thumbnail URL on the source site.'),
29
+ })
30
+ .loose()
31
+ .optional()
32
+ .describe('Thumbnail image references (present when enable_source_metadata is true).'),
33
+ })
34
+ .loose();
35
+ export const LlmContextSearchApiResponseSchema = z.object({
36
+ grounding: z.object({
37
+ generic: z
38
+ .array(z.object({
39
+ url: z.url(),
40
+ title: z.string(),
41
+ snippets: SnippetsSchema,
42
+ }))
43
+ .describe(`Array of LLM context items with extracted web content.`),
44
+ poi: z
45
+ .object({
46
+ name: z.string(),
47
+ url: z.url(),
48
+ title: z.string(),
49
+ snippets: SnippetsSchema,
50
+ })
51
+ .nullable()
52
+ .optional()
53
+ .describe(`A point of interest item returned for local recall queries.`),
54
+ map: z
55
+ .array(z.object({
56
+ name: z.string(),
57
+ url: z.union([z.url(), z.literal('')]),
58
+ title: z.string(),
59
+ snippets: SnippetsSchema,
60
+ }))
61
+ .describe(`Map/place results. When enable_local is enabled, the response may include map data.`),
62
+ }),
63
+ sources: z
64
+ .record(z.url(), SourceMetadataSchema)
65
+ .describe(`Metadata for each referenced URL, keyed by URL. Known fields include title, hostname, and age; site_name, favicon, and thumbnail are present when enable_source_metadata is true. Unknown fields are preserved as-is.`),
66
+ });
@@ -16,7 +16,7 @@ export const description = `
16
16
  - Phone numbers and opening hours
17
17
 
18
18
  Use this when the query implies 'near me', 'in my area', or mentions specific locations (e.g., 'in San Francisco'). This tool automatically falls back to brave_web_search if no local results are found.
19
- `;
19
+ `.trim();
20
20
  // Access to Local API is available through the Pro plans.
21
21
  export const execute = async (params) => {
22
22
  // Make sure both 'web' and 'locations' are in the result_filter
@@ -23,7 +23,7 @@ export const description = `
23
23
  - "According to [Reuters](https://www.reuters.com/technology/china-bans/), China bans uncertified and recalled power banks on planes".
24
24
  - "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%".
25
25
  - "According to [BBC News](https://www.bbc.com/news/world-europe-65910000), the UK government has announced a new policy to support renewable energy".
26
- `;
26
+ `.trim();
27
27
  export const execute = async (params) => {
28
28
  const response = await API.issueRequest('news', params);
29
29
  return {
@@ -45,8 +45,12 @@ export const params = z.object({
45
45
  .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'.")
46
46
  .optional(),
47
47
  freshness: z
48
- .union([z.literal('pd'), z.literal('pw'), z.literal('pm'), z.literal('py'), z.string()])
49
- .default('pd')
48
+ .union([
49
+ z.enum(['pd', 'pw', 'pm', 'py']),
50
+ z
51
+ .string()
52
+ .regex(/^\d{4}-\d{2}-\d{2}to\d{4}-\d{2}-\d{2}$/, "Use 'pd', 'pw', 'pm', or 'py' for a relative discovery window, or a custom range as YYYY-MM-DDtoYYYY-MM-DD (e.g. 2022-04-01to2022-07-30)."),
53
+ ])
50
54
  .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.")
51
55
  .optional(),
52
56
  extra_snippets: z
@@ -55,8 +59,8 @@ export const params = z.object({
55
59
  .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.')
56
60
  .optional(),
57
61
  goggles: z
58
- .array(z.string())
59
- .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).")
62
+ .union([z.string(), z.array(z.string())])
63
+ .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. Multiple goggle URLs and/or definitions can be provided in an array. For more details, refer to the Goggles repository (i.e., https://github.com/brave/goggles-quickstart).")
60
64
  .optional(),
61
65
  });
62
66
  export default params;
@@ -0,0 +1,52 @@
1
+ import API from '../../BraveAPI/index.js';
2
+ import { RequestParamsSchema, RequestHeadersSchema, PlaceSearchInputSchema, } from './schemas/input.js';
3
+ import { PlaceSearchApiResponseSchema } from './schemas/output.js';
4
+ export const name = 'brave_place_search';
5
+ export const annotations = {
6
+ title: 'Brave Place Search',
7
+ openWorldHint: true,
8
+ };
9
+ export const description = `
10
+ Searches Brave's Place Search API. A single call may populate any combination of 'results' (POIs), 'cities', 'addresses', 'streets', and 'location' (the resolved search area), depending on the query's shape.
11
+
12
+ When to use:
13
+ - POIs near coordinates or a named area (e.g. "coffee shops in Paris") -> 'results', each with structured business data (postal address, hours, contact, ratings, photos, categories, timezone).
14
+ - Browsing general POIs (omit 'query'; supply 'latitude'+'longitude' or 'location').
15
+ - Disambiguating a bare city name (e.g. "springfield") -> 'cities'.
16
+ - Resolving a specific address (e.g. "350 5th avenue" with NYC coords) -> 'addresses' (often plus 'streets').
17
+ - Looking up a street by name (e.g. "michigan avenue" with Chicago coords) -> 'streets'.
18
+
19
+ Inputs:
20
+ - Anchor the search via 'latitude'+'longitude' or 'location' (or both). With neither, 'query' is required.
21
+ - 'addresses' / 'streets' only surface when the query is address-/street-shaped AND geographically anchored.
22
+ - 'location' format: US -- '<city> <state> <country>' (e.g. 'san francisco ca united states'); non-US -- '<city> <country>' (e.g. 'tokyo japan'). Capitalization and commas don't matter.
23
+ - 'count' caps results (max 50, default 20). 'radius' (meters) biases toward closer results; it does NOT hard-limit the search area.
24
+ `.trim();
25
+ export const execute = async (params) => {
26
+ const parsedParams = RequestParamsSchema.parse(params);
27
+ const parsedHeaders = RequestHeadersSchema.parse(params);
28
+ const response = await API.issueRequest('placeSearch', parsedParams, parsedHeaders);
29
+ return {
30
+ content: [{ type: 'text', text: JSON.stringify(response) }],
31
+ isError: false,
32
+ structuredContent: response,
33
+ };
34
+ };
35
+ export const register = (mcpServer) => {
36
+ mcpServer.registerTool(name, {
37
+ title: name,
38
+ description: description,
39
+ inputSchema: PlaceSearchInputSchema.shape,
40
+ outputSchema: PlaceSearchApiResponseSchema.shape,
41
+ annotations: annotations,
42
+ }, execute);
43
+ };
44
+ export default {
45
+ name,
46
+ description,
47
+ annotations,
48
+ inputSchema: PlaceSearchInputSchema.shape,
49
+ outputSchema: PlaceSearchApiResponseSchema.shape,
50
+ execute,
51
+ register,
52
+ };
@@ -0,0 +1,216 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Keep up-to-date with documentation:
4
+ * https://api-dashboard.search.brave.com/api-reference/web/place_search
5
+ */
6
+ const CountryCodesSchema = z.enum([
7
+ 'AR',
8
+ 'AU',
9
+ 'AT',
10
+ 'BE',
11
+ 'BR',
12
+ 'CA',
13
+ 'CL',
14
+ 'DK',
15
+ 'FI',
16
+ 'FR',
17
+ 'DE',
18
+ 'GR',
19
+ 'HK',
20
+ 'IN',
21
+ 'ID',
22
+ 'IT',
23
+ 'JP',
24
+ 'KR',
25
+ 'MY',
26
+ 'MX',
27
+ 'NL',
28
+ 'NZ',
29
+ 'NO',
30
+ 'CN',
31
+ 'PL',
32
+ 'PT',
33
+ 'PH',
34
+ 'RU',
35
+ 'SA',
36
+ 'ZA',
37
+ 'ES',
38
+ 'SE',
39
+ 'CH',
40
+ 'TW',
41
+ 'TR',
42
+ 'GB',
43
+ 'US',
44
+ ]);
45
+ const SearchLangCodesSchema = z.enum([
46
+ 'ar',
47
+ 'eu',
48
+ 'bn',
49
+ 'bg',
50
+ 'ca',
51
+ 'zh-hans',
52
+ 'zh-hant',
53
+ 'hr',
54
+ 'cs',
55
+ 'da',
56
+ 'nl',
57
+ 'en',
58
+ 'en-gb',
59
+ 'et',
60
+ 'fi',
61
+ 'fr',
62
+ 'gl',
63
+ 'de',
64
+ 'el',
65
+ 'gu',
66
+ 'he',
67
+ 'hi',
68
+ 'hu',
69
+ 'is',
70
+ 'it',
71
+ 'jp',
72
+ 'kn',
73
+ 'ko',
74
+ 'lv',
75
+ 'lt',
76
+ 'ms',
77
+ 'ml',
78
+ 'mr',
79
+ 'nb',
80
+ 'pl',
81
+ 'pt-br',
82
+ 'pt-pt',
83
+ 'pa',
84
+ 'ro',
85
+ 'ru',
86
+ 'sr',
87
+ 'sk',
88
+ 'sl',
89
+ 'es',
90
+ 'sv',
91
+ 'ta',
92
+ 'te',
93
+ 'th',
94
+ 'tr',
95
+ 'uk',
96
+ 'vi',
97
+ ]);
98
+ const UiLangCodesSchema = z.enum([
99
+ 'es-AR',
100
+ 'en-AU',
101
+ 'de-AT',
102
+ 'nl-BE',
103
+ 'fr-BE',
104
+ 'pt-BR',
105
+ 'en-CA',
106
+ 'fr-CA',
107
+ 'es-CL',
108
+ 'da-DK',
109
+ 'fi-FI',
110
+ 'fr-FR',
111
+ 'de-DE',
112
+ 'zh-HK',
113
+ 'en-IN',
114
+ 'en-ID',
115
+ 'it-IT',
116
+ 'ja-JP',
117
+ 'ko-KR',
118
+ 'en-MY',
119
+ 'es-MX',
120
+ 'nl-NL',
121
+ 'en-NZ',
122
+ 'no-NO',
123
+ 'zh-CN',
124
+ 'pl-PL',
125
+ 'en-PH',
126
+ 'ru-RU',
127
+ 'en-ZA',
128
+ 'es-ES',
129
+ 'sv-SE',
130
+ 'fr-CH',
131
+ 'de-CH',
132
+ 'zh-TW',
133
+ 'tr-TR',
134
+ 'en-GB',
135
+ 'en-US',
136
+ 'es-US',
137
+ ]);
138
+ export const RequestParamsSchema = z.object({
139
+ query: z
140
+ .string()
141
+ .trim()
142
+ .max(400)
143
+ .refine((str) => str.length === 0 || str.split(/\s+/).length <= 50, 'Query cannot exceed 50 words')
144
+ .transform((str) => (str.length === 0 ? undefined : str))
145
+ .describe('Query string. Shape influences the response: POI-like queries -> `results`; bare/ambiguous city names -> `cities`; address- or street-shaped queries with a geographic anchor -> `addresses` and/or `streets`. If omitted, returns general POIs in the supplied area.')
146
+ .optional(),
147
+ radius: z
148
+ .number()
149
+ .min(0)
150
+ .describe('Bias toward results closer to the supplied coordinates, in meters. NOT a hard cutoff -- the API may still return more distant results. If omitted, the search is performed globally.')
151
+ .optional(),
152
+ count: z
153
+ .number()
154
+ .int()
155
+ .min(1)
156
+ .max(50)
157
+ .describe('Number of results to return. Maximum is 50. Default is 20.')
158
+ .optional(),
159
+ latitude: z
160
+ .number()
161
+ .min(-90)
162
+ .max(90)
163
+ .describe('Latitude of the geographical coordinates around which to search, in degrees (-90 to 90). Typically paired with `longitude`.')
164
+ .optional(),
165
+ longitude: z
166
+ .number()
167
+ .min(-180)
168
+ .max(180)
169
+ .describe('Longitude of the geographical coordinates around which to search, in degrees (-180 to 180). Typically paired with `latitude`.')
170
+ .optional(),
171
+ location: z
172
+ .string()
173
+ .trim()
174
+ .min(1)
175
+ .describe("Location string to search around, used as an alternative to `latitude` and `longitude`. For US locations prefer the form '<city> <state> <country name>' (e.g. 'san francisco ca united states'); for non-US locations use '<city> <country name>' (e.g. 'tokyo japan'). No commas or special characters needed; capitalization does not matter.")
176
+ .optional(),
177
+ country: CountryCodesSchema.describe("Two-letter country code (ISO 3166-1 alpha-2) used to scope the search. Defaults to 'US'.").optional(),
178
+ search_lang: SearchLangCodesSchema.describe("Language for the search results. Defaults to 'en'.").optional(),
179
+ ui_lang: UiLangCodesSchema.describe("User interface language for the response, usually of the form '<language>-<region>'. Defaults to 'en-US'.").optional(),
180
+ units: z
181
+ .enum(['metric', 'imperial'])
182
+ .describe("Units of measurement for distance values. Defaults to 'metric'.")
183
+ .optional(),
184
+ safesearch: z
185
+ .enum(['off', 'moderate', 'strict'])
186
+ .describe("Safe search level for the query results. 'off' - No filtering. 'moderate' - Filter out explicit content. 'strict' - Filter out explicit and suggestive content. Defaults to 'strict'.")
187
+ .optional(),
188
+ spellcheck: z
189
+ .boolean()
190
+ .describe('Whether to apply spellcheck before executing the search. Defaults to true.')
191
+ .optional(),
192
+ geoloc: z.string().describe('Optional geolocation token used to refine results.').optional(),
193
+ });
194
+ export const RequestHeadersSchema = z.object({
195
+ 'api-version': z
196
+ .string()
197
+ .regex(/^\d{4}-\d{2}-\d{2}$/)
198
+ .describe('The API version to use. This is denoted by the format YYYY-MM-DD. Default is the latest that is available. Read more about API versioning at https://api-dashboard.search.brave.com/documentation/guides/versioning.')
199
+ .optional(),
200
+ accept: z
201
+ .enum(['application/json', '*/*'])
202
+ .describe('The default supported media type is application/json.')
203
+ .optional(),
204
+ 'cache-control': z
205
+ .literal('no-cache')
206
+ .describe('Brave Search will return cached content by default. To prevent caching set the Cache-Control header to no-cache. This is currently done as best effort.')
207
+ .optional(),
208
+ 'user-agent': z
209
+ .string()
210
+ .describe('The user agent originating the request. Brave Search can utilize the user agent to provide a different experience depending on the device as described by the string. The user agent should follow the commonly used browser agent strings on each platform. For more information on curating user agents, see RFC 9110.')
211
+ .optional(),
212
+ });
213
+ export const PlaceSearchInputSchema = z.object({
214
+ ...RequestParamsSchema.shape,
215
+ ...RequestHeadersSchema.shape,
216
+ });
@@ -0,0 +1,249 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Keep up to date with documentation:
4
+ * https://api-dashboard.search.brave.com/api-reference/web/place_search#responses
5
+ */
6
+ const CoordinatesSchema = z
7
+ .tuple([z.number(), z.number()])
8
+ .describe('Latitude/longitude pair for the location, when available.');
9
+ const ThumbnailSchema = z.looseObject({
10
+ src: z.string().describe('The served URL of the picture thumbnail.'),
11
+ alt: z.string().describe('The alt text for the thumbnail.').optional(),
12
+ height: z.number().describe('The height of the thumbnail.').optional(),
13
+ width: z.number().describe('The width of the thumbnail.').optional(),
14
+ bg_color: z.string().describe('The background color of the thumbnail.').optional(),
15
+ original: z.url().describe('The original URL of the image.').optional(),
16
+ logo: z.boolean().describe('Whether the thumbnail is a logo.').optional(),
17
+ duplicated: z.boolean().describe('Whether the thumbnail is duplicated.').optional(),
18
+ theme: z.string().describe('The theme of the thumbnail (e.g. "light", "dark").').optional(),
19
+ });
20
+ const PostalAddressSchema = z.looseObject({
21
+ type: z.literal('PostalAddress'),
22
+ country: z.string().describe('The country associated with the location.').optional(),
23
+ postalCode: z.string().describe('The postal code associated with the location.').optional(),
24
+ streetAddress: z.string().describe('The street address associated with the location.').optional(),
25
+ addressRegion: z
26
+ .string()
27
+ .describe('The region associated with the location. Usually a state.')
28
+ .optional(),
29
+ addressLocality: z
30
+ .string()
31
+ .describe('The address locality or subregion associated with the location.')
32
+ .optional(),
33
+ displayAddress: z.string().describe('The displayed address string.'),
34
+ });
35
+ const DayOpeningHoursSchema = z.looseObject({
36
+ abbr_name: z.string().describe('Short name of the day of the week (e.g. "Mon").').optional(),
37
+ full_name: z.string().describe('Full name of the day of the week (e.g. "Monday").').optional(),
38
+ opens: z.string().describe('Opening time in 24h format (e.g. "09:00").').optional(),
39
+ closes: z.string().describe('Closing time in 24h format (e.g. "17:00").').optional(),
40
+ });
41
+ const OpeningHoursSchema = z.looseObject({
42
+ current_day: z
43
+ .array(DayOpeningHoursSchema)
44
+ .describe('Opening hours for the current day. May contain multiple entries when the location closes and reopens during the day.')
45
+ .optional(),
46
+ days: z
47
+ .array(z.union([DayOpeningHoursSchema, z.array(DayOpeningHoursSchema)]))
48
+ .describe('Opening hours for the rest of the week. Each entry may itself be either a single day-hours object or an array of day-hours objects (when a day has multiple open/close intervals).')
49
+ .optional(),
50
+ });
51
+ const ContactSchema = z.looseObject({
52
+ email: z.string().describe('Contact email for the business.').optional(),
53
+ telephone: z.string().describe('Contact telephone number for the business.').optional(),
54
+ });
55
+ const DistanceSchema = z.looseObject({
56
+ value: z.number().describe('The quantity of the unit.'),
57
+ units: z.string().describe('The name of the unit associated with the quantity.'),
58
+ });
59
+ const ProfileSchema = z.looseObject({
60
+ type: z.string().optional(),
61
+ name: z.string().optional(),
62
+ long_name: z.string().optional(),
63
+ url: z.string().optional(),
64
+ img: z.string().optional(),
65
+ });
66
+ const RatingSchema = z.looseObject({
67
+ ratingValue: z.number().describe('The current value of the rating.').optional(),
68
+ bestRating: z.number().describe('Highest possible rating value.').optional(),
69
+ reviewCount: z.number().describe('Number of reviews backing the rating.').optional(),
70
+ profile: ProfileSchema.optional(),
71
+ is_tripadvisor: z
72
+ .boolean()
73
+ .describe('Whether the rating originates from Tripadvisor.')
74
+ .optional(),
75
+ });
76
+ const ReviewSchema = z.looseObject({
77
+ title: z.string().describe('The title of the review.'),
78
+ description: z.string().describe('A description seen in the review.'),
79
+ date: z.string().describe('The date when the review was published.'),
80
+ rating: RatingSchema.optional(),
81
+ author: ProfileSchema.optional(),
82
+ });
83
+ const ReviewsSchema = z.looseObject({
84
+ results: z
85
+ .array(ReviewSchema)
86
+ .describe('A list of trip advisor reviews for the entity.')
87
+ .optional(),
88
+ viewMoreUrl: z
89
+ .string()
90
+ .describe('A URL to a web page where more information on the result can be seen.')
91
+ .optional(),
92
+ reviews_in_foreign_language: z
93
+ .boolean()
94
+ .describe('Any reviews available in a foreign language.')
95
+ .optional(),
96
+ });
97
+ const PicturesSchema = z.looseObject({
98
+ viewMoreUrl: z.string().describe('URL where additional pictures can be viewed.').optional(),
99
+ results: z.array(ThumbnailSchema).describe('Thumbnail entries for the location.').optional(),
100
+ });
101
+ const ActionSchema = z.looseObject({
102
+ type: z.string().describe('The type representing the action.'),
103
+ url: z.string().describe('A URL representing the action to be taken.'),
104
+ });
105
+ const MetaUrlSchema = z.looseObject({
106
+ scheme: z.string().describe('The protocol scheme extracted from the URL.'),
107
+ netloc: z.string().describe('The network location part extracted from the URL.'),
108
+ hostname: z.string().describe('The lowercased domain name extracted from the URL.').optional(),
109
+ favicon: z.string().describe('The favicon used for the URL.').optional(),
110
+ path: z
111
+ .string()
112
+ .describe('The hierarchical path of the URL useful as a display string.')
113
+ .optional(),
114
+ });
115
+ /**
116
+ * Fields shared by every "web-style" result entry: both inline web results
117
+ * (which decorate location results via `results`) and the location/POI
118
+ * results themselves. Defined once and composed via `.extend()` below.
119
+ */
120
+ const WebResultBaseSchema = z.looseObject({
121
+ title: z.string().describe('The display title of the location.'),
122
+ url: z
123
+ .union([z.url(), z.literal('')])
124
+ .describe('Primary URL associated with the location. May be an empty string.'),
125
+ is_source_local: z.boolean().describe('Whether the result is from local sources.'),
126
+ is_source_both: z.boolean().describe('Whether the result is from both local and global sources.'),
127
+ description: z
128
+ .string()
129
+ .describe('Short description of the location (e.g. "Plaza", "Theater").')
130
+ .optional(),
131
+ page_age: z.string().describe('The age of the page as a date string.').optional(),
132
+ page_fetched: z
133
+ .string()
134
+ .describe('The date the page was last fetched as a date string.')
135
+ .optional(),
136
+ fetched_content_timestamp: z
137
+ .number()
138
+ .describe('The timestamp of the content when it was fetched.')
139
+ .optional(),
140
+ profile: ProfileSchema.describe('The profile associated with the result.').optional(),
141
+ language: z.string().describe('The language of the page.').optional(),
142
+ family_friendly: z.boolean().describe('Whether the result is family friendly.').optional(),
143
+ });
144
+ /**
145
+ * An inline web result attached to a location (under `LocationResultSchema.results`).
146
+ */
147
+ const WebResultSchema = WebResultBaseSchema.extend({
148
+ meta_url: MetaUrlSchema.optional(),
149
+ });
150
+ /**
151
+ * A location/POI result. Used both at the top level (`results[]`) and inside
152
+ * addresses (`pois[]` / `pois_nearby[]`).
153
+ */
154
+ const LocationResultSchema = WebResultBaseSchema.extend({
155
+ type: z.literal('location_result').describe('Result type identifier.'),
156
+ provider_url: z
157
+ .union([z.url(), z.literal('')])
158
+ .describe('URL of the upstream provider for this result. May be an empty string.'),
159
+ coordinates: CoordinatesSchema.optional(),
160
+ zoom_level: z.number().describe('Suggested zoom level when displaying on a map.'),
161
+ thumbnail: ThumbnailSchema.describe('The thumbnail associated with the location.').optional(),
162
+ postal_address: PostalAddressSchema.describe('The postal address of the location.').optional(),
163
+ opening_hours: OpeningHoursSchema.describe('The opening hours of the location.').optional(),
164
+ contact: ContactSchema.describe('The contact of the location.').optional(),
165
+ price_range: z
166
+ .string()
167
+ .describe('Price classification string for the business (e.g. "$", "$$").')
168
+ .optional(),
169
+ rating: RatingSchema.describe('The rating of the result.').optional(),
170
+ distance: DistanceSchema.describe("Distance from the user's geolocation, if available.").optional(),
171
+ profiles: z
172
+ .array(ProfileSchema)
173
+ .describe('External profiles (e.g. data providers) associated with the result.')
174
+ .optional(),
175
+ reviews: ReviewsSchema.describe('Reviews associated with the result.').optional(),
176
+ pictures: PicturesSchema.describe('Pictures associated with the result.').optional(),
177
+ action: ActionSchema.describe('The action associated with the result.').optional(),
178
+ serves_cuisine: z.array(z.string()).describe('List of cuisine categories served.').optional(),
179
+ categories: z.array(z.string()).describe('List of category labels.').optional(),
180
+ icon_category: z.string().describe('Suggested icon category (e.g. "cafe").').optional(),
181
+ timezone: z.string().describe('IANA timezone identifier for the location.').optional(),
182
+ timezone_offset: z
183
+ .number()
184
+ .describe("UTC offset of the location's timezone, in minutes.")
185
+ .optional(),
186
+ id: z
187
+ .string()
188
+ .describe('Temporary identifier for the location, valid for ~8 hours. Can be used with brave_local_search-style endpoints to fetch additional information.')
189
+ .optional(),
190
+ results: z.array(WebResultSchema).describe('Web results related to this location.').optional(),
191
+ });
192
+ const QuerySchema = z.looseObject({
193
+ original: z
194
+ .string()
195
+ .describe('The original query string as supplied by the caller (may be empty).'),
196
+ altered: z
197
+ .string()
198
+ .describe('The altered query string as supplied by the caller (may be empty).')
199
+ .optional(),
200
+ spellcheck_off: z.boolean().optional(),
201
+ show_strict_warning: z.boolean().optional(),
202
+ });
203
+ const AddressSchema = z.looseObject({
204
+ type: z
205
+ .enum(['address', 'street'])
206
+ .describe('Result is "address" when the result refers to an explicit street + number, but "street" when it refers only to the street itself.'),
207
+ name: z.string().describe('The name of the address.'),
208
+ coordinates: CoordinatesSchema.describe('Latitude/longitude of the address'),
209
+ pois: z.array(LocationResultSchema).describe('List of POIs located at this address.'),
210
+ pois_nearby: z.array(LocationResultSchema).describe('List of POIs nearby this address.'),
211
+ zoom_level: z.number().describe('Suggested zoom level when displaying on a map.'),
212
+ distance: DistanceSchema.describe("Distance from the user's geolocation, if available.").optional(),
213
+ postal_address: PostalAddressSchema.describe('The postal address of the address.').optional(),
214
+ });
215
+ const CitySchema = z.looseObject({
216
+ type: z.literal('city'),
217
+ name: z.string().describe('Name of the city'),
218
+ country: z.string().describe('ISO country code for the city'),
219
+ coordinates: CoordinatesSchema.describe('Latitude/longitude of the city'),
220
+ thumbnail: ThumbnailSchema.describe('Primary image for the city'),
221
+ });
222
+ const LocationSchema = z.looseObject({
223
+ coordinates: CoordinatesSchema.describe('Latitude/longitude of the resolved search area.'),
224
+ name: z.string().describe('Human-readable name of the resolved search area.').optional(),
225
+ country: z.string().describe('ISO country code for the resolved search area.').optional(),
226
+ });
227
+ export const PlaceSearchApiResponseSchema = z.looseObject({
228
+ type: z
229
+ .literal('locations')
230
+ .describe('Top-level response discriminator. Always "locations" for Place Search.'),
231
+ query: QuerySchema.optional(),
232
+ results: z
233
+ .array(LocationResultSchema)
234
+ .describe('Points of interest matching the search. Populated for POI-shaped queries.')
235
+ .optional(),
236
+ cities: z
237
+ .array(CitySchema)
238
+ .describe('City matches for the query. Typically populated when the query is a bare or ambiguous city name (e.g. "springfield", "san francisco").')
239
+ .optional(),
240
+ addresses: z
241
+ .array(AddressSchema)
242
+ .describe('Address matches. Typically populated when the query is a street + number AND is geographically anchored via `latitude`+`longitude` or a specific `location`.')
243
+ .optional(),
244
+ streets: z
245
+ .array(AddressSchema)
246
+ .describe('Street matches. Typically populated when the query is a street name AND is geographically anchored via `latitude`+`longitude` or a specific `location`.')
247
+ .optional(),
248
+ location: LocationSchema.describe('The search area as resolved by the API (e.g. coordinates + city name). Useful for confirming the API interpreted the input as expected and for grounding follow-up queries.').optional(),
249
+ });
@@ -18,7 +18,7 @@ export const description = `
18
18
  Returns a text summary that consolidates information from the search results. Optional features include inline references to source URLs and additional entity information.
19
19
 
20
20
  Requirements: Must first perform a web search using brave_web_search with summary=true parameter. Requires a Pro AI subscription to access the summarizer functionality.
21
- `;
21
+ `.trim();
22
22
  export const execute = async (params) => {
23
23
  const response = { content: [], isError: false };
24
24
  try {
@@ -14,7 +14,7 @@ export const description = `
14
14
  - Useful for discovering video content, getting video metadata, or finding videos from specific creators/publishers.
15
15
 
16
16
  Returns a JSON list of video-related results with title, url, description, duration, and thumbnail_url.
17
- `;
17
+ `.trim();
18
18
  export const execute = async (params) => {
19
19
  const response = await API.issueRequest('videos', params);
20
20
  return {
@@ -46,7 +46,12 @@ export const params = z.object({
46
46
  .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'.")
47
47
  .optional(),
48
48
  freshness: z
49
- .union([z.literal('pd'), z.literal('pw'), z.literal('pm'), z.literal('py'), z.string()])
49
+ .union([
50
+ z.enum(['pd', 'pw', 'pm', 'py']),
51
+ z
52
+ .string()
53
+ .regex(/^\d{4}-\d{2}-\d{2}to\d{4}-\d{2}-\d{2}$/, "Use 'pd', 'pw', 'pm', or 'py' for a relative discovery window, or a custom range as YYYY-MM-DDtoYYYY-MM-DD (e.g. 2022-04-01to2022-07-30)."),
54
+ ])
50
55
  .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').")
51
56
  .optional(),
52
57
  });