Advertisement
Guest User

revised index.js

a guest
Mar 15th, 2025
58
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.08 KB | None | 0 0
  1. #!/usr/bin/env node
  2.  
  3. import { Server } from "@modelcontextprotocol/sdk/server/index.js";
  4. import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  5. import {
  6. CallToolRequestSchema,
  7. ListToolsRequestSchema,
  8. ListResourcesRequestSchema,
  9. ListPromptsRequestSchema
  10. } from "@modelcontextprotocol/sdk/types.js";
  11. import https from 'node:https';
  12. import { URL } from 'node:url';
  13.  
  14. // Uncomment if needed for corporate proxies
  15. process.env.HTTPS_PROXY = 'http://your-zscaler-proxy:port';
  16. process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; // Only for debugging
  17.  
  18. const WEB_SEARCH_TOOL = {
  19. name: "brave_web_search",
  20. description: "Performs a web search using the Brave Search API, ideal for general queries, news, articles, and online content.",
  21. inputSchema: {
  22. type: "object",
  23. properties: {
  24. query: {
  25. type: "string",
  26. description: "Search query (max 400 chars, 50 words)"
  27. },
  28. count: {
  29. type: "number",
  30. description: "Number of results (1-20, default 10)",
  31. default: 10
  32. },
  33. offset: {
  34. type: "number",
  35. description: "Pagination offset (max 9, default 0)",
  36. default: 0
  37. },
  38. },
  39. required: ["query"],
  40. },
  41. };
  42.  
  43. const LOCAL_SEARCH_TOOL = {
  44. name: "brave_local_search",
  45. description: "Searches for local businesses and places using Brave's Local Search API.",
  46. inputSchema: {
  47. type: "object",
  48. properties: {
  49. query: {
  50. type: "string",
  51. description: "Local search query (e.g. 'pizza near Central Park')"
  52. },
  53. count: {
  54. type: "number",
  55. description: "Number of results (1-20, default 5)",
  56. default: 5
  57. },
  58. },
  59. required: ["query"]
  60. }
  61. };
  62.  
  63. // Server implementation
  64. const server = new Server({
  65. name: "example-servers/brave-search",
  66. version: "0.1.0",
  67. }, {
  68. capabilities: {
  69. tools: {},
  70. resources: {},
  71. prompts: {}
  72. },
  73. });
  74.  
  75. // Check for API key
  76. const BRAVE_API_KEY = process.env.BRAVE_API_KEY;
  77. if (!BRAVE_API_KEY) {
  78. console.error("Error: BRAVE_API_KEY environment variable is required");
  79. process.exit(1);
  80. }
  81.  
  82. const RATE_LIMIT = {
  83. perSecond: 1,
  84. perMonth: 15000
  85. };
  86.  
  87. let requestCount = {
  88. second: 0,
  89. month: 0,
  90. lastReset: Date.now()
  91. };
  92.  
  93. function checkRateLimit() {
  94. const now = Date.now();
  95. if (now - requestCount.lastReset > 1000) {
  96. requestCount.second = 0;
  97. requestCount.lastReset = now;
  98. }
  99. if (requestCount.second >= RATE_LIMIT.perSecond ||
  100. requestCount.month >= RATE_LIMIT.perMonth) {
  101. throw new Error('Rate limit exceeded');
  102. }
  103. requestCount.second++;
  104. requestCount.month++;
  105. }
  106.  
  107. function isBraveWebSearchArgs(args) {
  108. return (typeof args === "object" &&
  109. args !== null &&
  110. "query" in args &&
  111. typeof args.query === "string");
  112. }
  113.  
  114. function isBraveLocalSearchArgs(args) {
  115. return (typeof args === "object" &&
  116. args !== null &&
  117. "query" in args &&
  118. typeof args.query === "string");
  119. }
  120.  
  121. // Helper function for HTTP requests
  122. function httpsRequest(url, options = {}, postData = null) {
  123. return new Promise((resolve, reject) => {
  124. console.error(`Making HTTPS request to: ${url}`);
  125.  
  126. // Set default headers if not provided
  127. if (!options.headers) {
  128. options.headers = {};
  129. }
  130.  
  131. // Use proper content negotiation
  132. options.headers['Accept'] = 'application/json';
  133.  
  134. // Do not use compression for debugging
  135. delete options.headers['Accept-Encoding'];
  136.  
  137. const req = https.request(url, options, (res) => {
  138. const chunks = [];
  139.  
  140. res.on('data', (chunk) => {
  141. chunks.push(chunk);
  142. });
  143.  
  144. res.on('end', () => {
  145. const body = Buffer.concat(chunks).toString();
  146. console.error(`Response status: ${res.statusCode}`);
  147.  
  148. if (res.statusCode >= 200 && res.statusCode < 300) {
  149. try {
  150. const data = JSON.parse(body);
  151. resolve({ statusCode: res.statusCode, data });
  152. } catch (e) {
  153. console.error(`Error parsing JSON: ${e.message}`);
  154. console.error(`Raw response body: ${body.substring(0, 200)}...`);
  155. reject(new Error(`Invalid JSON response: ${e.message}`));
  156. }
  157. } else {
  158. console.error(`Error response: ${body}`);
  159. reject(new Error(`HTTP Error: ${res.statusCode} ${res.statusMessage}`));
  160. }
  161. });
  162. });
  163.  
  164. req.on('error', (error) => {
  165. console.error(`Request error: ${error.message}`);
  166. reject(error);
  167. });
  168.  
  169. if (postData) {
  170. req.write(postData);
  171. }
  172.  
  173. req.end();
  174. });
  175. }
  176.  
  177. async function performWebSearch(query, count = 10, offset = 0) {
  178. checkRateLimit();
  179. console.error(`Performing web search for query: ${query}`);
  180.  
  181. const searchUrl = new URL('https://api.search.brave.com/res/v1/web/search');
  182. searchUrl.searchParams.set('q', query);
  183. searchUrl.searchParams.set('count', Math.min(count, 20).toString());
  184. searchUrl.searchParams.set('offset', offset.toString());
  185. searchUrl.searchParams.set('mcp', 'true');
  186.  
  187. console.error(`Request URL: ${searchUrl.toString()}`);
  188.  
  189. try {
  190. const options = {
  191. method: 'GET',
  192. headers: {
  193. 'X-Subscription-Token': BRAVE_API_KEY,
  194. 'User-Agent': 'Mozilla/5.0 MCP Client'
  195. }
  196. };
  197.  
  198. const { statusCode, data } = await httpsRequest(searchUrl, options);
  199. console.error(`Response data structure: ${Object.keys(data).join(', ')}`);
  200.  
  201. // Just return the raw results for debugging
  202. return JSON.stringify(data, null, 2);
  203. } catch (error) {
  204. console.error(`Search error: ${error.message}`);
  205. throw error;
  206. }
  207. }
  208.  
  209. async function performLocalSearch(query, count = 5) {
  210. checkRateLimit();
  211. console.error(`Performing local search for query: ${query}`);
  212.  
  213. // Initial search to get location IDs
  214. const webUrl = new URL('https://api.search.brave.com/res/v1/web/search');
  215. webUrl.searchParams.set('q', query);
  216. webUrl.searchParams.set('search_lang', 'en');
  217. webUrl.searchParams.set('result_filter', 'locations');
  218. webUrl.searchParams.set('count', Math.min(count, 20).toString());
  219.  
  220. try {
  221. const options = {
  222. method: 'GET',
  223. headers: {
  224. 'Accept': 'application/json',
  225. 'Accept-Encoding': 'gzip',
  226. 'X-Subscription-Token': BRAVE_API_KEY
  227. }
  228. };
  229.  
  230. const { data: webData } = await httpsRequest(webUrl, options);
  231. const locationIds = webData.locations?.results?.filter((r) => r.id != null).map(r => r.id) || [];
  232.  
  233. if (locationIds.length === 0) {
  234. console.error('No location IDs found, falling back to web search');
  235. return performWebSearch(query, count); // Fallback to web search
  236. }
  237.  
  238. // Get POI details and descriptions in parallel
  239. const [poisData, descriptionsData] = await Promise.all([
  240. getPoisData(locationIds),
  241. getDescriptionsData(locationIds)
  242. ]);
  243.  
  244. return formatLocalResults(poisData, descriptionsData);
  245. } catch (error) {
  246. console.error(`Local search error: ${error.message}`);
  247. throw error;
  248. }
  249. }
  250.  
  251. async function getPoisData(ids) {
  252. checkRateLimit();
  253. const url = new URL('https://api.search.brave.com/res/v1/local/pois');
  254. ids.filter(Boolean).forEach(id => url.searchParams.append('ids', id));
  255.  
  256. const options = {
  257. method: 'GET',
  258. headers: {
  259. 'Accept': 'application/json',
  260. 'Accept-Encoding': 'gzip',
  261. 'X-Subscription-Token': BRAVE_API_KEY
  262. }
  263. };
  264.  
  265. const { data } = await httpsRequest(url, options);
  266. return data;
  267. }
  268.  
  269. async function getDescriptionsData(ids) {
  270. checkRateLimit();
  271. const url = new URL('https://api.search.brave.com/res/v1/local/descriptions');
  272. ids.filter(Boolean).forEach(id => url.searchParams.append('ids', id));
  273.  
  274. const options = {
  275. method: 'GET',
  276. headers: {
  277. 'Accept': 'application/json',
  278. 'Accept-Encoding': 'gzip',
  279. 'X-Subscription-Token': BRAVE_API_KEY
  280. }
  281. };
  282.  
  283. const { data } = await httpsRequest(url, options);
  284. return data;
  285. }
  286.  
  287. function formatLocalResults(poisData, descData) {
  288. return (poisData.results || []).map(poi => {
  289. const address = [
  290. poi.address?.streetAddress ?? '',
  291. poi.address?.addressLocality ?? '',
  292. poi.address?.addressRegion ?? '',
  293. poi.address?.postalCode ?? ''
  294. ].filter(part => part !== '').join(', ') || 'N/A';
  295. return `Name: ${poi.name}
  296. Address: ${address}
  297. Phone: ${poi.phone || 'N/A'}
  298. Rating: ${poi.rating?.ratingValue ?? 'N/A'} (${poi.rating?.ratingCount ?? 0} reviews)
  299. Price Range: ${poi.priceRange || 'N/A'}
  300. Hours: ${(poi.openingHours || []).join(', ') || 'N/A'}
  301. Description: ${descData.descriptions[poi.id] || 'No description available'}
  302. `;
  303. }).join('\n---\n') || 'No local results found';
  304. }
  305.  
  306. // Tool handlers
  307. server.setRequestHandler(ListToolsRequestSchema, async () => ({
  308. tools: [WEB_SEARCH_TOOL, LOCAL_SEARCH_TOOL],
  309. }));
  310.  
  311. // Add handlers for the missing methods
  312. server.setRequestHandler(ListResourcesRequestSchema, async () => ({
  313. resources: [] // Empty list as this isn't implemented
  314. }));
  315.  
  316. server.setRequestHandler(ListPromptsRequestSchema, async () => ({
  317. prompts: [] // Empty list as this isn't implemented
  318. }));
  319.  
  320. server.setRequestHandler(CallToolRequestSchema, async (request) => {
  321. try {
  322. console.error(`Received tool call: ${JSON.stringify(request.params)}`);
  323. const { name, arguments: args } = request.params;
  324. if (!args) {
  325. throw new Error("No arguments provided");
  326. }
  327. switch (name) {
  328. case "brave_web_search": {
  329. if (!isBraveWebSearchArgs(args)) {
  330. throw new Error("Invalid arguments for brave_web_search");
  331. }
  332. console.error(`Processing brave_web_search with args: ${JSON.stringify(args)}`);
  333. const { query, count = 10 } = args;
  334. const results = await performWebSearch(query, count);
  335. console.error(`Search completed successfully`);
  336. return {
  337. content: [{ type: "text", text: results }],
  338. isError: false,
  339. };
  340. }
  341. case "brave_local_search": {
  342. if (!isBraveLocalSearchArgs(args)) {
  343. throw new Error("Invalid arguments for brave_local_search");
  344. }
  345. const { query, count = 5 } = args;
  346. const results = await performLocalSearch(query, count);
  347. return {
  348. content: [{ type: "text", text: results }],
  349. isError: false,
  350. };
  351. }
  352. default:
  353. return {
  354. content: [{ type: "text", text: `Unknown tool: ${name}` }],
  355. isError: true,
  356. };
  357. }
  358. }
  359. catch (error) {
  360. return {
  361. content: [
  362. {
  363. type: "text",
  364. text: `Error: ${error instanceof Error ? error.message : String(error)}`,
  365. },
  366. ],
  367. isError: true,
  368. };
  369. }
  370. });
  371.  
  372. async function runServer() {
  373. const transport = new StdioServerTransport();
  374. await server.connect(transport);
  375. console.error("Brave Search MCP Server running on stdio");
  376. }
  377.  
  378. runServer().catch((error) => {
  379. console.error("Fatal error running server:", error);
  380. process.exit(1);
  381. });
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement