续之前文章:
<工具 Claude Desktop> 配置 MCP server 连接本地 SQLite, 本机文件夹(目录) 网络驱动器 Windows 11 系统-CSDN博客
就这审查制度,能排到北朝鲜是因为它们更严。
配置 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
1. 前提
- 保留 filesystem
- filesystem 使用的是 网络驱动器
2. 新建 Brave search 文件夹
原因:
- filesystem server 与 Brave search server 是两个不同的 MCP 服务实现
- 它们使用各个独立的依赖
- 它们的配件不同
- 它们使用不同的工具
创建 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 node
import { 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 implementation
const server = new Server(
{
name: "example-servers/brave-search",
version: "0.1.0",
},
{
capabilities: {
tools: {},
},
},
);
// Check for API key
const 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 handlers
server.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 里有给链接:https://brave.com/search/api/
* 验证 Brave Search
1. 网上搜索
prompt: Can you search for recent trends in generative AI?
2 搜索本地内容, 因为我用 VXN 所在带上地名 p
prompt: 北京最古老的清真寺
{
`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%BA
Title: 牛街 - 维基百科,自由的百科全书
Description: 牛街是中国北京市西城区南部的一条南北走向的大街。北起广安门内大街南至南横街,因为在这条街上多穆斯林聚居而闻名,亦因为在这条街上拥有牛街礼拜寺而闻名于世。 · 牛街这个名称的历史远远没有这条街悠久,明...
URL: https://zh.wikipedia.org/zh/%E7%89%9B%E8%A1%97
Title: “牛街”的由来与北京最大的清真寺_建筑
Description: <strong>牛街礼拜寺</strong>位于北京市西城区广安门内牛街,占地6000平方米,是北京规模最大、历史最久的清真寺,也是世界上著名的清真寺之一。
URL: https://www.sohu.com/a/419716451_168296
Title: 牛街
Description: 牛街是北京市西城区牛街...京规模最大、历史最久的清真寺,<strong>始建于北宋至道二年(996年),明正统七年(1442年)重修,清康三十五年(1696年)大修,近年又有修缮装饰</strong>。寺院面积不大,但建筑集中、对称。...
URL: https://baike.baidu.com/item/%E7%89%9B%E8%A1%97/22993
Title: 牛街清真寺
Description: 牛街清真寺位于广安门内牛街。是北京规模最大、历史最久的一座清真寺,<strong>创建于辽圣宗十三年(966),宋太宗至道元年(995)、明正统七年(1442)重修</strong>。清康熙三十五年(1696)又按原样进行大规模修葺。主要建筑有礼拜殿、梆歌楼...
URL: https://s.visitbeijing.com.cn/attraction/101846
Title: 北京最大的回民区,被称为吃货天堂,拥有北京最古老的清真寺_牛街
Description: 作为北京最大的回民聚集区,牛街历史悠久,这里聚居着以回族为主的20多个少数民族,建筑风格和颜色具有民族特色,北京规模最大、历史最久的国家重点文物之一牛街礼拜寺就坐落其中。
URL: https://www.sohu.com/a/440856633_100143624
Title: 牛街,绝对是北京吃货的天堂_豆汁_红豆_红枣
Description: 在宣南有块独特的宝地,那就是牛街。 · 别看牛街只是一条不长的街道,可这里却有二十多个民族的人生活在这里。牛街地区胡同连着胡同,小胡同大约60余条,其中回族住户相对集中的胡同有30余条。
URL: https://www.sohu.com/a/615474822_121117451
Title: Niujie Mosque, Beijing
Description: 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. more
URL: https://www.tripadvisor.com/Attraction_Review-g294212-d1979771-Reviews-Niujie_Mosque-Beijing.html
Title: 牛街礼拜寺_牛街礼拜寺_首都之窗_北京市人民政府门户网站
Description: 牛街礼拜寺,是回族伊斯兰建筑,居北京四大清真寺之首。牛街清真寺的总平面布局很有特点。寺在牛街东侧,大殿必须坐西向东,入口就只能设在殿的后面。寺门以望月楼代替,楼前有木牌楼三间,隔街为照壁,以强调入口。
URL: https://www.beijing.gov.cn/renwen/rwzyd/gdwh/njlbj/202107/t20210726_2448729.html
Title: 探秘北京牛街礼拜寺_赏心悦目_首都之窗_北京市人民政府门户网站
Description: 牛街礼拜寺位于北京广安门内牛街,是北京历史最悠久、规模最大的清真寺,也是世界上著名的清真寺之一。喜欢它中国传统的木结构式以及浓厚的伊斯兰风格,神秘而静谧,正因古寺游客少,可以静心细看这明清古建,...
URL: https://www.beijing.gov.cn/tsbj/sxym/202111/t20211130_2549950.html
可以看到搜索源是中文世界,还有英文的搜索词。可能是我用了 data for AI / suggestion
** 验证 Filesystem
总结:
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