工具Claude_Desktop>_配置Brave_Search_MCP_Server

508 阅读8分钟

配置 Brave Search MCP Server

什么是 Brave Search MCP

Brave Search MCP 作为 Claude Desktop 和 Brave search 搜索引擎之间的连接。这种集成将Claude 与 MCP 的结构化连接无缝对接,让用户能够轻松访问网络和本地搜索。

Brave Search MCP 提供多种功能,包括:

  • 实时网络信息检索
  • 基于上下文的本地搜索,以提供更优质的结果
  • 尊重隐私的 AI 辅助研究

翻译的这个文章中的段落内容:自己看完 MCP Integration: How Brave Search and Claude Desktop Enhance AI Assistant Agentic Capabilities

Image 2

1. 前提

  • 保留 filesystem
  • filesystem 使用的是 网络驱动器

2. 新建 Brave search 文件夹

原因:

  1. filesystem server 与 Brave search server 是两个不同的 MCP 服务实现
  2. 它们使用各个独立的依赖
  3. 它们的配件不同
  4. 它们使用不同的工具

创建 Brave search 文件夹

我使用:  C:\Users\dave\AppData\Roaming\Claude\Brave_search
我推荐使用本地目录,如果是映射驱动器或网路路径,要用 UNC 表达

C:\Users\<your ID>\AppData\Roaming\Claude\Brave_search

放这里,只是为了不离着近,不会删除目录。

3. 三个 node.js 配置文件放到上面的目录里面

Github 项目文件:servers/src/brave-search at main · modelcontextprotocol/servers · GitHub

自己用的,放到这里做为参考:

1)index.ts

这个文件没有变化

#!/usr/bin/env nodeimport { Server } from "@modelcontextprotocol/sdk/server/index.js";import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";import {  CallToolRequestSchema,  ListToolsRequestSchema,  Tool,} from "@modelcontextprotocol/sdk/types.js";const WEB_SEARCH_TOOL: Tool = {  name: "brave_web_search",  description:"Performs a web search using the Brave Search API, ideal for general queries, news, articles, and online content. " +"Use this for broad information gathering, recent events, or when you need diverse web sources. " +"Supports pagination, content filtering, and freshness controls. " +"Maximum 20 results per request, with offset for pagination. ",  inputSchema: {type: "object",    properties: {      query: {type: "string",        description: "Search query (max 400 chars, 50 words)"      },count: {type: "number",        description: "Number of results (1-20, default 10)",default: 10      },      offset: {type: "number",        description: "Pagination offset (max 9, default 0)",default: 0      },    },    required: ["query"],  },};const LOCAL_SEARCH_TOOL: Tool = {  name: "brave_local_search",  description:"Searches for local businesses and places using Brave's Local Search API. " +"Best for queries related to physical locations, businesses, restaurants, services, etc. " +"Returns detailed information including:\n" +"- Business names and addresses\n" +"- Ratings and review counts\n" +"- Phone numbers and opening hours\n" +"Use this when the query implies 'near me' or mentions specific locations. " +"Automatically falls back to web search if no local results are found.",  inputSchema: {type: "object",    properties: {      query: {type: "string",        description: "Local search query (e.g. 'pizza near Central Park')"      },count: {type: "number",        description: "Number of results (1-20, default 5)",default: 5      },    },    required: ["query"]  }};// Server implementationconst server = new Server(  {    name: "example-servers/brave-search",    version: "0.1.0",  },  {    capabilities: {      tools: {},    },  },);// Check for API keyconst BRAVE_API_KEY = process.env.BRAVE_API_KEY!;if (!BRAVE_API_KEY) {  console.error("Error: BRAVE_API_KEY environment variable is required");  process.exit(1);}const RATE_LIMIT = {  perSecond: 1,  perMonth: 15000};let requestCount = {  second: 0,  month: 0,  lastReset: Date.now()};function checkRateLimit() {  const now = Date.now();if (now - requestCount.lastReset > 1000) {    requestCount.second = 0;    requestCount.lastReset = now;  }if (requestCount.second >= RATE_LIMIT.perSecond ||    requestCount.month >= RATE_LIMIT.perMonth) {    throw new Error('Rate limit exceeded');  }  requestCount.second++;  requestCount.month++;}interface BraveWeb {  web?: {    results?: Array<{      title: string;      description: string;      url: string;      language?: string;      published?: string;      rank?: number;    }>;  };  locations?: {    results?: Array<{      id: string; // Required by API      title?: string;    }>;  };}interface BraveLocation {  id: string;  name: string;address: {    streetAddress?: string;    addressLocality?: string;    addressRegion?: string;    postalCode?: string;  };  coordinates?: {    latitude: number;    longitude: number;  };  phone?: string;  rating?: {    ratingValue?: number;    ratingCount?: number;  };  openingHours?: string[];  priceRange?: string;}interface BravePoiResponse {  results: BraveLocation[];}interface BraveDescription {  descriptions: {[id: string]: string};}function isBraveWebSearchArgs(args: unknown): args is { query: string; count?: number } {return (    typeof args === "object" &&    args !== null &&"query" in args &&    typeof (args as { query: string }).query === "string"  );}function isBraveLocalSearchArgs(args: unknown): args is { query: string; count?: number } {return (    typeof args === "object" &&    args !== null &&"query" in args &&    typeof (args as { query: string }).query === "string"  );}async function performWebSearch(query: string, count: number = 10, offset: number = 0) {  checkRateLimit();  const url = new URL('https://api.search.brave.com/res/v1/web/search');  url.searchParams.set('q', query);  url.searchParams.set('count', Math.min(count, 20).toString()); // API limit  url.searchParams.set('offset', offset.toString());  const response = await fetch(url, {    headers: {'Accept': 'application/json','Accept-Encoding': 'gzip','X-Subscription-Token': BRAVE_API_KEY    }  });if (!response.ok) {    throw new Error(`Brave API error: ${response.status} ${response.statusText}\n${await response.text()}`);  }  const data = await response.json() as BraveWeb;// Extract just web results  const results = (data.web?.results || []).map(result => ({    title: result.title || '',    description: result.description || '',    url: result.url || ''  }));return results.map(r =>    `Title: ${r.title}\nDescription: ${r.description}\nURL: ${r.url}`  ).join('\n\n');}async function performLocalSearch(query: string, count: number = 5) {  checkRateLimit();// Initial search to get location IDs  const webUrl = new URL('https://api.search.brave.com/res/v1/web/search');  webUrl.searchParams.set('q', query);  webUrl.searchParams.set('search_lang', 'en');  webUrl.searchParams.set('result_filter', 'locations');  webUrl.searchParams.set('count', Math.min(count, 20).toString());  const webResponse = await fetch(webUrl, {    headers: {'Accept': 'application/json','Accept-Encoding': 'gzip','X-Subscription-Token': BRAVE_API_KEY    }  });if (!webResponse.ok) {    throw new Error(`Brave API error: ${webResponse.status} ${webResponse.statusText}\n${await webResponse.text()}`);  }  const webData = await webResponse.json() as BraveWeb;  const locationIds = webData.locations?.results?.filter((r): r is {id: string; title?: string} => r.id != null).map(r => r.id) || [];if (locationIds.length === 0) {return performWebSearch(query, count); // Fallback to web search  }// Get POI details and descriptions in parallel  const [poisData, descriptionsData] = await Promise.all([    getPoisData(locationIds),    getDescriptionsData(locationIds)  ]);return formatLocalResults(poisData, descriptionsData);}async function getPoisData(ids: string[]): Promise<BravePoiResponse> {  checkRateLimit();  const url = new URL('https://api.search.brave.com/res/v1/local/pois');  ids.filter(Boolean).forEach(id => url.searchParams.append('ids', id));  const response = await fetch(url, {    headers: {'Accept': 'application/json','Accept-Encoding': 'gzip','X-Subscription-Token': BRAVE_API_KEY    }  });if (!response.ok) {    throw new Error(`Brave API error: ${response.status} ${response.statusText}\n${await response.text()}`);  }  const poisResponse = await response.json() as BravePoiResponse;return poisResponse;}async function getDescriptionsData(ids: string[]): Promise<BraveDescription> {  checkRateLimit();  const url = new URL('https://api.search.brave.com/res/v1/local/descriptions');  ids.filter(Boolean).forEach(id => url.searchParams.append('ids', id));  const response = await fetch(url, {    headers: {'Accept': 'application/json','Accept-Encoding': 'gzip','X-Subscription-Token': BRAVE_API_KEY    }  });if (!response.ok) {    throw new Error(`Brave API error: ${response.status} ${response.statusText}\n${await response.text()}`);  }  const descriptionsData = await response.json() as BraveDescription;return descriptionsData;}function formatLocalResults(poisData: BravePoiResponse, descData: BraveDescription): string {return (poisData.results || []).map(poi => {    const address = [      poi.address?.streetAddress ?? '',      poi.address?.addressLocality ?? '',      poi.address?.addressRegion ?? '',      poi.address?.postalCode ?? ''    ].filter(part => part !== '').join(', ') || 'N/A';return `Name: ${poi.name}Address: ${address}Phone: ${poi.phone || 'N/A'}Rating: ${poi.rating?.ratingValue ?? 'N/A'} (${poi.rating?.ratingCount ?? 0} reviews)Price Range: ${poi.priceRange || 'N/A'}Hours: ${(poi.openingHours || []).join(', ') || 'N/A'}Description: ${descData.descriptions[poi.id] || 'No description available'}`;  }).join('\n---\n') || 'No local results found';}// Tool handlersserver.setRequestHandler(ListToolsRequestSchema, async () => ({  tools: [WEB_SEARCH_TOOL, LOCAL_SEARCH_TOOL],}));server.setRequestHandler(CallToolRequestSchema, async (request) => {  try {    const { name, arguments: args } = request.params;if (!args) {      throw new Error("No arguments provided");    }    switch (name) {      case "brave_web_search": {if (!isBraveWebSearchArgs(args)) {          throw new Error("Invalid arguments for brave_web_search");        }        const { query, count = 10 } = args;        const results = await performWebSearch(query, count);return {content: [{ type: "text", text: results }],          isError: false,        };      }      case "brave_local_search": {if (!isBraveLocalSearchArgs(args)) {          throw new Error("Invalid arguments for brave_local_search");        }        const { query, count = 5 } = args;        const results = await performLocalSearch(query, count);return {content: [{ type: "text", text: results }],          isError: false,        };      }default:return {content: [{ type: "text", text: `Unknown tool: ${name}` }],          isError: true,        };    }  } catch (error) {return {content: [        {type: "text",          text: `Error: ${error instanceof Error ? error.message : String(error)}`,        },      ],      isError: true,    };  }});async function runServer() {  const transport = new StdioServerTransport();  await server.connect(transport);  console.error("Brave Search MCP Server running on stdio");}runServer().catch((error) => {  console.error("Fatal error running server:", error);  process.exit(1);});

2)package.json

{"name": "@modelcontextprotocol/server-brave-search","version": "0.6.2","description": "MCP server for Brave Search API integration","type": "module","bin": {"mcp-server-brave-search": "dist/index.js"  },"scripts": {"build": "tsc && shx chmod +x dist/*.js","prepare": "npm run build","watch": "tsc --watch"  },"dependencies": {"@modelcontextprotocol/sdk": "1.0.1"  },"devDependencies": {"@types/node": "^20.10.0","shx": "^0.3.4","typescript": "^5.6.2"  }}

3)tsconfig.json

{"compilerOptions": {"outDir": "./dist","rootDir": ".","target": "ES2022","module": "NodeNext","moduleResolution": "NodeNext","strict": true,"esModuleInterop": true,"skipLibCheck": true,"forceConsistentCasingInFileNames": true  },"include": ["./**/*.ts"  ],"exclude": ["node_modules"  ]}

4. claude_desktop_config.json 配置文件 (含 filesystem MCP)

{"mcpServers": {"filesystem": {"command": "npx","args": [ "-y", "@modelcontextprotocol/server-filesystem", "\\\\davens\\Multimedia\\2024-MyProgramFiles"            ]        },"brave-search": {"command": "npx","args": ["-y","@modelcontextprotocol/server-brave-search"            ],"env": {"BRAVE_API_KEY": "用自己的KEY"            }        }    }}

5. 申请 Brave Search Free KEY

这个在 Github 里有给链接:brave.com/search/api/

Image 7

* 验证 Brave Search

1. 网上搜索

prompt:  Can you search for recent trends in generative AI?

Image 8

Image 9

2 搜索本地内容, 因为我用 VXN 所在带上地名 p

prompt: 北京最古老的清真寺

Image 10

{  `query`: `北京最古老的清真寺 历史 牛街`}{  `query`: `北京最古老的清真寺 历史 牛街`}Title: 牛街礼拜寺 - 维基百科,自由的百科全书Description: 据说,牛街礼拜寺始建于辽朝统和十四年(996年),由阿拉伯学者纳苏鲁丁(那速鲁定)创建。《北京牛街冈儿上礼拜寺志》(简称《冈志》)刘仲泉补志记载:URL: https://zh.wikipedia.org/zh-hans/%E7%89%9B%E8%A1%97%E7%A4%BC%E6%8B%9C%E5%AF%BATitle: 牛街 - 维基百科,自由的百科全书Description: 牛街是中国北京市西城区南部的一条南北走向的大街。北起广安门内大街南至南横街,因为在这条街上多穆斯林聚居而闻名,亦因为在这条街上拥有牛街礼拜寺而闻名于世。 · 牛街这个名称的历史远远没有这条街悠久,明...URL: https://zh.wikipedia.org/zh/%E7%89%9B%E8%A1%97Title: “牛街”的由来与北京最大的清真寺_建筑Description: <strong>牛街礼拜寺</strong>位于北京市西城区广安门内牛街,占地6000平方米,是北京规模最大、历史最久的清真寺,也是世界上著名的清真寺之一。URL: https://www.sohu.com/a/419716451_168296Title: 牛街Description: 牛街是北京市西城区牛街...京规模最大、历史最久的清真寺,<strong>始建于北宋至道二年(996年),明正统七年(1442年)重修,清康三十五年(1696年)大修,近年又有修缮装饰</strong>。寺院面积不大,但建筑集中、对称。...URL: https://baike.baidu.com/item/%E7%89%9B%E8%A1%97/22993Title: 牛街清真寺Description: 牛街清真寺位于广安门内牛街。是北京规模最大、历史最久的一座清真寺,<strong>创建于辽圣宗十三年(966),宋太宗至道元年(995)、明正统七年(1442)重修</strong>。清康熙三十五年(1696)又按原样进行大规模修葺。主要建筑有礼拜殿、梆歌楼...URL: https://s.visitbeijing.com.cn/attraction/101846Title: 北京最大的回民区,被称为吃货天堂,拥有北京最古老的清真寺_牛街Description: 作为北京最大的回民聚集区,牛街历史悠久,这里聚居着以回族为主的20多个少数民族,建筑风格和颜色具有民族特色,北京规模最大、历史最久的国家重点文物之一牛街礼拜寺就坐落其中。URL: https://www.sohu.com/a/440856633_100143624Title: 牛街,绝对是北京吃货的天堂_豆汁_红豆_红枣Description: 在宣南有块独特的宝地,那就是牛街。 · 别看牛街只是一条不长的街道,可这里却有二十多个民族的人生活在这里。牛街地区胡同连着胡同,小胡同大约60余条,其中回族住户相对集中的胡同有30余条。URL: https://www.sohu.com/a/615474822_121117451Title: Niujie Mosque, BeijingDescription: This is the version of our website addressed to speakers of English in the United States. If you are a resident of another country or region, please select the appropriate version of Tripadvisor for your country or region in the drop-down menu. moreURL: https://www.tripadvisor.com/Attraction_Review-g294212-d1979771-Reviews-Niujie_Mosque-Beijing.htmlTitle: 牛街礼拜寺_牛街礼拜寺_首都之窗_北京市人民政府门户网站Description: 牛街礼拜寺,是回族伊斯兰建筑,居北京四大清真寺之首。牛街清真寺的总平面布局很有特点。寺在牛街东侧,大殿必须坐西向东,入口就只能设在殿的后面。寺门以望月楼代替,楼前有木牌楼三间,隔街为照壁,以强调入口。URL: https://www.beijing.gov.cn/renwen/rwzyd/gdwh/njlbj/202107/t20210726_2448729.htmlTitle: 探秘北京牛街礼拜寺_赏心悦目_首都之窗_北京市人民政府门户网站Description: 牛街礼拜寺位于北京广安门内牛街,是北京历史最悠久、规模最大的清真寺,也是世界上著名的清真寺之一。喜欢它中国传统的木结构式以及浓厚的伊斯兰风格,神秘而静谧,正因古寺游客少,可以静心细看这明清古建,...URL: https://www.beijing.gov.cn/tsbj/sxym/202111/t20211130_2549950.html

可以看到搜索源是中文世界,还有英文的搜索词。可能是我用了 data for AI / suggestion

Image 12

** 验证 Filesystem 

Image 13

总结:

Brave Search 使用 Free 订购要链接付款账号。

测试时,关闭 Claude Desktop App 需要在任务管理器结束 task (Claud Node.js如果有),再启动 Claude Desktop App。

Brave Search 没有计数器, 稍微上点儿隐就要送 刀乐。

有闲心从推荐从这三个里,找一个练练手。

  • Google Maps - Location services, directions, and place details
  • Memory - Knowledge graph-based persistent memory system
  • PostgreSQL - Read-only database access with schema inspection