Skip to main content
Glama

SearchAPI MCP Server

index.js25 kB
const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js'); const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js'); const axios = require('axios'); const { z } = require('zod'); // 从环境变量中读取 API Key const SEARCHAPI_API_KEY = process.env.SEARCHAPI_API_KEY; if (!SEARCHAPI_API_KEY) { console.error('错误:请设置环境变量 SEARCHAPI_API_KEY'); // 如果希望在API Key缺失时阻止服务器启动,可以取消下面的注释 // process.exit(1); } // 常量 const SEARCHAPI_URL = 'https://www.searchapi.io/api/v1/search'; // 初始化 MCP 服务器 const server = new McpServer({ name: 'searchapi', version: '1.0.0' }); /** * 向searchapi.io发送请求并处理错误情况 * @param {Object} params - 请求参数 * @returns {Promise<Object>} - 响应数据或错误信息 */ async function makeSearchapiRequest(params) { // 确保API Key被添加到参数中 params.api_key = SEARCHAPI_API_KEY; try { const response = await axios.get(SEARCHAPI_URL, { params, timeout: 30000 // 30秒超时 }); return response.data; } catch (error) { let errorDetail = null; if (error.response) { try { errorDetail = error.response.data; } catch (e) { errorDetail = error.response.statusText; } } const errorMessage = `调用searchapi.io时出错: ${error.message}`; if (errorDetail) { return { error: `${errorMessage}, 详情: ${JSON.stringify(errorDetail)}` }; } return { error: errorMessage }; } } // 实现工具函数:搜索Google地图 server.tool( 'search_google_maps', { query: z.string().describe('搜索查询'), location_ll: z.string().optional().describe('位置坐标,格式为"纬度,经度"') }, async ({ query, location_ll }) => { const params = { engine: 'google_maps', q: query }; if (location_ll) { params.ll = location_ll; } const result = await makeSearchapiRequest(params); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } ); // 实现工具函数:搜索Google航班 server.tool( 'search_google_flights', { departure_id: z.string().describe('出发地ID'), arrival_id: z.string().describe('目的地ID'), outbound_date: z.string().describe('出发日期'), flight_type: z.string().default('round_trip').describe('航班类型'), return_date: z.string().optional().describe('返程日期'), gl: z.string().optional().describe('地理位置'), hl: z.string().optional().describe('语言'), currency: z.string().optional().describe('货币'), travel_class: z.string().optional().describe('舱位等级'), stops: z.string().optional().describe('中转次数'), sort_by: z.string().optional().describe('排序方式'), adults: z.string().optional().describe('成人数量'), children: z.string().optional().describe('儿童数量'), multi_city_json: z.string().optional().describe('多城市行程JSON'), show_cheapest_flights: z.string().optional().describe('显示最便宜航班'), show_hidden_flights: z.string().optional().describe('显示隐藏航班'), max_price: z.string().optional().describe('最高价格'), carry_on_bags: z.string().optional().describe('随身行李'), checked_bags: z.string().optional().describe('托运行李'), included_airlines: z.string().optional().describe('包含的航空公司'), excluded_airlines: z.string().optional().describe('排除的航空公司'), outbound_times: z.string().optional().describe('出发时间范围'), return_times: z.string().optional().describe('返程时间范围'), emissions: z.string().optional().describe('排放量'), included_connecting_airports: z.string().optional().describe('包含的中转机场'), excluded_connecting_airports: z.string().optional().describe('排除的中转机场'), layover_duration_min: z.string().optional().describe('最短中转时间'), layover_duration_max: z.string().optional().describe('最长中转时间'), max_flight_duration: z.string().optional().describe('最长飞行时间'), separate_tickets: z.string().optional().describe('分开的机票'), infants_in_seat: z.string().optional().describe('占座婴儿数量'), infants_on_lap: z.string().optional().describe('不占座婴儿数量'), departure_token: z.string().optional().describe('出发令牌'), booking_token: z.string().optional().describe('预订令牌') }, async (args) => { const params = { engine: 'google_flights', flight_type: args.flight_type }; // 处理flight_type不同情况下的必填参数 if (args.flight_type === 'multi_city') { if (!args.multi_city_json) { return { content: [{ type: 'text', text: JSON.stringify({ error: '多城市行程需要"multi_city_json"参数' }) }], isError: true }; } params.multi_city_json = args.multi_city_json; } else { params.departure_id = args.departure_id; params.arrival_id = args.arrival_id; params.outbound_date = args.outbound_date; if (args.flight_type === 'round_trip') { if (!args.return_date) { return { content: [{ type: 'text', text: JSON.stringify({ error: '往返行程需要"return_date"参数' }) }], isError: true }; } params.return_date = args.return_date; } } // 添加其他可选参数 const optionalParams = [ 'gl', 'hl', 'currency', 'travel_class', 'stops', 'sort_by', 'adults', 'children', 'show_cheapest_flights', 'show_hidden_flights', 'max_price', 'carry_on_bags', 'checked_bags', 'included_airlines', 'excluded_airlines', 'outbound_times', 'return_times', 'emissions', 'included_connecting_airports', 'excluded_connecting_airports', 'layover_duration_min', 'layover_duration_max', 'max_flight_duration', 'separate_tickets', 'infants_in_seat', 'infants_on_lap', 'departure_token', 'booking_token' ]; for (const key of optionalParams) { if (args[key] !== undefined) { params[key] = args[key]; } } const result = await makeSearchapiRequest(params); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } ); // 实现工具函数:搜索Google酒店 server.tool( 'search_google_hotels', { q: z.string().describe('搜索查询'), check_in_date: z.string().describe('入住日期'), check_out_date: z.string().describe('退房日期'), gl: z.string().optional().describe('地理位置'), hl: z.string().optional().describe('语言'), currency: z.string().optional().describe('货币'), property_type: z.string().optional().describe('物业类型'), sort_by: z.string().optional().describe('排序方式'), price_min: z.string().optional().describe('最低价格'), price_max: z.string().optional().describe('最高价格'), property_types: z.string().optional().describe('物业类型列表'), amenities: z.string().optional().describe('设施'), rating: z.string().optional().describe('评分'), free_cancellation: z.string().optional().describe('免费取消'), special_offers: z.string().optional().describe('特别优惠'), for_displaced_individuals: z.string().optional().describe('为流离失所的个人'), eco_certified: z.string().optional().describe('生态认证'), hotel_class: z.string().optional().describe('酒店等级'), brands: z.string().optional().describe('品牌'), bedrooms: z.string().optional().describe('卧室数量'), bathrooms: z.string().optional().describe('浴室数量'), adults: z.string().optional().describe('成人数量'), children_ages: z.string().optional().describe('儿童年龄'), next_page_token: z.string().optional().describe('下一页令牌') }, async (args) => { const params = { engine: 'google_hotels', q: args.q, check_in_date: args.check_in_date, check_out_date: args.check_out_date }; // 添加可选参数 const optionalParams = [ 'gl', 'hl', 'currency', 'property_type', 'sort_by', 'price_min', 'price_max', 'property_types', 'amenities', 'rating', 'free_cancellation', 'special_offers', 'for_displaced_individuals', 'eco_certified', 'hotel_class', 'brands', 'bedrooms', 'bathrooms', 'adults', 'children_ages', 'next_page_token' ]; for (const key of optionalParams) { if (args[key] !== undefined) { params[key] = args[key]; } } const result = await makeSearchapiRequest(params); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } ); // 实现工具函数:搜索Google地图评论 server.tool( 'search_google_maps_reviews', { place_id: z.string().optional().describe('地点ID'), data_id: z.string().optional().describe('数据ID'), topic_id: z.string().optional().describe('主题ID'), next_page_token: z.string().optional().describe('下一页令牌'), sort_by: z.string().optional().describe('排序方式'), rating: z.string().optional().describe('评分'), hl: z.string().optional().describe('语言'), gl: z.string().optional().describe('地理位置'), reviews_limit: z.string().optional().describe('评论数量限制') }, async (args) => { const params = { engine: 'google_maps_reviews' }; // 检查必填参数 if (args.place_id) { params.place_id = args.place_id; } else if (args.data_id) { params.data_id = args.data_id; } else { return { content: [{ type: 'text', text: JSON.stringify({ error: '必须提供place_id或data_id参数' }) }], isError: true }; } // 添加可选参数 const optionalParams = [ 'topic_id', 'next_page_token', 'sort_by', 'rating', 'hl', 'gl', 'reviews_limit' ]; for (const key of optionalParams) { if (args[key] !== undefined) { params[key] = args[key]; } } const result = await makeSearchapiRequest(params); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } ); // 实现工具函数:查询Google酒店详细信息 server.tool( 'search_google_hotels_property', { property_token: z.string().describe('物业令牌'), check_in_date: z.string().describe('入住日期'), check_out_date: z.string().describe('退房日期'), gl: z.string().optional().describe('地理位置'), hl: z.string().optional().describe('语言'), currency: z.string().optional().describe('货币'), adults: z.string().optional().describe('成人数量'), children: z.string().optional().describe('儿童数量'), children_ages: z.string().optional().describe('儿童年龄') }, async (args) => { const params = { engine: 'google_hotels_property', property_token: args.property_token, check_in_date: args.check_in_date, check_out_date: args.check_out_date }; // 添加可选参数 const optionalParams = [ 'gl', 'hl', 'currency', 'adults', 'children', 'children_ages' ]; for (const key of optionalParams) { if (args[key] !== undefined) { params[key] = args[key]; } } const result = await makeSearchapiRequest(params); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } ); // 实现工具函数:查询Google航班日历价格 server.tool( 'search_google_flights_calendar', { flight_type: z.string().describe('航班类型'), departure_id: z.string().describe('出发地ID'), arrival_id: z.string().describe('目的地ID'), outbound_date: z.string().describe('出发日期'), return_date: z.string().optional().describe('返程日期'), outbound_date_start: z.string().optional().describe('出发日期开始'), outbound_date_end: z.string().optional().describe('出发日期结束'), return_date_start: z.string().optional().describe('返程日期开始'), return_date_end: z.string().optional().describe('返程日期结束'), gl: z.string().optional().describe('地理位置'), hl: z.string().optional().describe('语言'), currency: z.string().optional().describe('货币'), adults: z.string().optional().describe('成人数量'), children: z.string().optional().describe('儿童数量'), travel_class: z.string().optional().describe('舱位等级'), stops: z.string().optional().describe('中转次数') }, async (args) => { const params = { engine: 'google_flights_calendar', flight_type: args.flight_type, departure_id: args.departure_id, arrival_id: args.arrival_id, outbound_date: args.outbound_date }; // 检查航班类型,确保提供必要参数 if (args.flight_type === 'round_trip' && !args.return_date) { return { content: [{ type: 'text', text: JSON.stringify({ error: '往返航班需要提供return_date参数' }) }], isError: true }; } else if (args.flight_type === 'round_trip') { params.return_date = args.return_date; } // 添加可选参数 const optionalParams = [ 'outbound_date_start', 'outbound_date_end', 'return_date_start', 'return_date_end', 'gl', 'hl', 'currency', 'adults', 'children', 'travel_class', 'stops' ]; for (const key of optionalParams) { if (args[key] !== undefined) { params[key] = args[key]; } } const result = await makeSearchapiRequest(params); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } ); // 实现工具函数:获取当前系统时间和旅行日期建议 server.tool( 'get_current_time', { format: z.string().default('iso').describe('日期格式'), days_offset: z.string().default('0').describe('日期偏移量'), return_future_dates: z.string().default('false').describe('是否返回未来日期'), future_days: z.string().default('7').describe('未来天数') }, async (args) => { const now = new Date(); // 将字符串参数转换为对应的数据类型 let daysOffsetInt; try { daysOffsetInt = args.days_offset ? parseInt(args.days_offset, 10) : 0; } catch (e) { return { content: [{ type: 'text', text: JSON.stringify({ error: 'days_offset必须是整数' }) }], isError: true }; } const returnFutureDatesBool = args.return_future_dates ? args.return_future_dates.toLowerCase() === 'true' : false; let futureDaysInt; try { futureDaysInt = args.future_days ? parseInt(args.future_days, 10) : 7; } catch (e) { return { content: [{ type: 'text', text: JSON.stringify({ error: 'future_days必须是整数' }) }], isError: true }; } // 计算目标日期 const targetDate = new Date(now); targetDate.setDate(targetDate.getDate() + daysOffsetInt); // 格式化日期的辅助函数 const formatDate = (date, format) => { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); switch (format) { case 'iso': return `${year}-${month}-${day}`; case 'slash': return `${day}/${month}/${year}`; case 'chinese': return `${year}年${month}月${day}日`; case 'timestamp': return Math.floor(date.getTime() / 1000); case 'full': return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; default: return `${year}-${month}-${day}`; } }; // 获取星期几 const getWeekday = (date, short = false) => { const weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; const shortWeekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; return short ? shortWeekdays[date.getDay()] : weekdays[date.getDay()]; }; // 构建结果对象 const result = { now: { iso: formatDate(now, 'iso'), slash: formatDate(now, 'slash'), chinese: formatDate(now, 'chinese'), timestamp: formatDate(now, 'timestamp'), full: formatDate(now, 'full'), time: `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`, weekday: getWeekday(now), weekday_short: getWeekday(now, true), year: now.getFullYear(), month: now.getMonth() + 1, day: now.getDate(), hour: now.getHours(), minute: now.getMinutes(), second: now.getSeconds() }, target_date: { iso: formatDate(targetDate, 'iso'), slash: formatDate(targetDate, 'slash'), chinese: formatDate(targetDate, 'chinese'), timestamp: formatDate(targetDate, 'timestamp'), full: formatDate(targetDate, 'full'), time: `${String(targetDate.getHours()).padStart(2, '0')}:${String(targetDate.getMinutes()).padStart(2, '0')}:${String(targetDate.getSeconds()).padStart(2, '0')}`, weekday: getWeekday(targetDate), weekday_short: getWeekday(targetDate, true), year: targetDate.getFullYear(), month: targetDate.getMonth() + 1, day: targetDate.getDate(), hour: targetDate.getHours(), minute: targetDate.getMinutes(), second: targetDate.getSeconds() } }; // 旅行场景常用日期 const tomorrow = new Date(now); tomorrow.setDate(tomorrow.getDate() + 1); const nextWeek = new Date(now); nextWeek.setDate(nextWeek.getDate() + 7); const nextMonth = new Date(now); nextMonth.setDate(nextMonth.getDate() + 30); // 计算下一个周五(周末开始) const weekend = new Date(now); weekend.setDate(weekend.getDate() + ((5 - weekend.getDay() + 7) % 7)); // 计算下一个周日(周末结束) const weekendEnd = new Date(now); weekendEnd.setDate(weekendEnd.getDate() + ((0 - weekendEnd.getDay() + 7) % 7)); result.travel_dates = { today: formatDate(now, 'iso'), tomorrow: formatDate(tomorrow, 'iso'), next_week: formatDate(nextWeek, 'iso'), next_month: formatDate(nextMonth, 'iso'), weekend: formatDate(weekend, 'iso'), weekend_end: formatDate(weekendEnd, 'iso') }; // 对于旅行预订常用的入住-退房日期对 const tomorrow2Days = new Date(tomorrow); tomorrow2Days.setDate(tomorrow2Days.getDate() + 2); const tomorrow7Days = new Date(tomorrow); tomorrow7Days.setDate(tomorrow7Days.getDate() + 7); const nextMonth2Days = new Date(nextMonth); nextMonth2Days.setDate(nextMonth2Days.getDate() + 2); result.hotel_stay_suggestions = [ { check_in: formatDate(tomorrow, 'iso'), check_out: formatDate(tomorrow2Days, 'iso'), nights: 2, description: '短暂周末度假' }, { check_in: formatDate(tomorrow, 'iso'), check_out: formatDate(tomorrow7Days, 'iso'), nights: 7, description: '一周度假' }, { check_in: formatDate(nextMonth, 'iso'), check_out: formatDate(nextMonth2Days, 'iso'), nights: 2, description: '下个月短暂出行' } ]; // 如果需要一系列未来日期 if (returnFutureDatesBool) { const futureDates = []; for (let i = 0; i < futureDaysInt; i++) { const futureDate = new Date(now); futureDate.setDate(futureDate.getDate() + i); futureDates.push({ iso: formatDate(futureDate, 'iso'), slash: formatDate(futureDate, 'slash'), chinese: formatDate(futureDate, 'chinese'), weekday: getWeekday(futureDate), weekday_short: getWeekday(futureDate, true), days_from_now: i }); } result.future_dates = futureDates; } // 使用请求的格式作为主要返回值 result.date = formatDate(targetDate, args.format || 'iso'); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } ); // 实现工具函数:搜索Google搜索结果 server.tool( 'search_google', { q: z.string().describe('搜索查询'), device: z.string().default('desktop').describe('设备类型'), location: z.string().optional().describe('位置'), uule: z.string().optional().describe('位置编码'), google_domain: z.string().default('google.com').describe('Google域名'), gl: z.string().default('us').describe('地理位置'), hl: z.string().default('en').describe('语言'), lr: z.string().optional().describe('语言限制'), cr: z.string().optional().describe('国家限制'), nfpr: z.string().default('0').describe('不进行拼写检查'), filter: z.string().default('1').describe('过滤结果'), safe: z.string().default('off').describe('安全搜索'), time_period: z.string().optional().describe('时间段'), time_period_min: z.string().optional().describe('最小时间段'), time_period_max: z.string().optional().describe('最大时间段'), num: z.string().default('10').describe('结果数量'), page: z.string().default('1').describe('页码') }, async (args) => { const params = { engine: 'google', q: args.q }; // 添加可选参数 const optionalParams = [ 'device', 'location', 'uule', 'google_domain', 'gl', 'hl', 'lr', 'cr', 'nfpr', 'filter', 'safe', 'time_period', 'time_period_min', 'time_period_max', 'num', 'page' ]; for (const key of optionalParams) { if (args[key] !== undefined) { params[key] = args[key]; } } const result = await makeSearchapiRequest(params); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } ); // 实现工具函数:搜索Google视频结果 server.tool( 'search_google_videos', { q: z.string().describe('搜索查询'), device: z.string().default('desktop').describe('设备类型'), location: z.string().optional().describe('位置'), uule: z.string().optional().describe('位置编码'), google_domain: z.string().default('google.com').describe('Google域名'), gl: z.string().default('us').describe('地理位置'), hl: z.string().default('en').describe('语言'), lr: z.string().optional().describe('语言限制'), cr: z.string().optional().describe('国家限制'), nfpr: z.string().default('0').describe('不进行拼写检查'), filter: z.string().default('1').describe('过滤结果'), safe: z.string().default('off').describe('安全搜索'), time_period: z.string().optional().describe('时间段'), time_period_min: z.string().optional().describe('最小时间段'), time_period_max: z.string().optional().describe('最大时间段'), num: z.string().default('10').describe('结果数量'), page: z.string().default('1').describe('页码') }, async (args) => { const params = { engine: 'google_videos', q: args.q }; // 添加可选参数 const optionalParams = [ 'device', 'location', 'uule', 'google_domain', 'gl', 'hl', 'lr', 'cr', 'nfpr', 'filter', 'safe', 'time_period', 'time_period_min', 'time_period_max', 'num', 'page' ]; for (const key of optionalParams) { if (args[key] !== undefined) { params[key] = args[key]; } } const result = await makeSearchapiRequest(params); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } ); // 启动服务器 async function main() { try { // 从环境变量获取 transport 类型,默认为 stdio const transport = process.env.MCP_TRANSPORT || 'stdio'; if (transport === 'stdio') { const stdioTransport = new StdioServerTransport(); await server.connect(stdioTransport); console.log('SearchAPI MCP 服务器已启动(stdio模式)'); } else { console.error(`不支持的传输类型: ${transport}`); process.exit(1); } } catch (error) { console.error('启动服务器时出错:', error); process.exit(1); } } // 运行服务器 main().catch(error => { console.error('未捕获的错误:', error); process.exit(1); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/lianshuang-photo/searchapi-mcp-nodejs'

If you have feedback or need assistance with the MCP directory API, please join our Discord server