import asyncio import json import logging import os import subprocess from typing import List, Dict from mcp.server.fastmcp import FastMCP logger = logging.getLogger(__name__) PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..")) NODE_BIN = "/usr/bin/node" BING_SCRIPT = r""" const p = require('puppeteer'); (async () => { const b = await p.launch({headless:true,args:['--no-sandbox','--disable-setuid-sandbox','--disable-blink-features=AutomationControlled']}); const page = await b.newPage(); await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'); await page.setExtraHTTPHeaders({'Accept-Language':'en-US,en;q=0.9'}); await page.evaluateOnNewDocument(() => { Object.defineProperty(navigator, 'webdriver', {get:()=>undefined}); }); const q = process.argv[process.argv.length - 2]; const max = parseInt(process.argv[process.argv.length - 1] || '10', 10); const sk = ['bing.com','google.com','facebook.com','twitter.com','instagram.com','youtube.com','reddit.com','amazon.com','wikipedia.org','baidu.com','linkedin.com','pinterest.com','ebay.com','walmart.com','w3.org','whatsapp.com','wechat.com','qq.com','taobao.com','tmall.com','alibaba.com','alipay.com','dict','dictionary','translate','zhihu.com','baike.baidu.com','sogou.com','163.com','sohu.com','sina.com','iciba.com','cambridge','britannica','sciencedirect','mdpi.com','springer','wiley.com','acm.org','ieee.org','researchgate','semanticscholar','ncbi.nlm.nih','nature.com','oup.com','sagepub','tandfonline']; try { await page.goto('https://cn.bing.com/search?q=' + encodeURIComponent(q) + '&setlang=en-US&cc=US', {waitUntil:'domcontentloaded',timeout:10000}); await page.waitForSelector('.b_algo', {timeout:5000}).catch(()=>{}); const results = await page.evaluate((m, sk) => { const reCJK = /[\u4e00-\u9fff\u3400-\u4dbf]/; const found = []; const seen = new Set(); document.querySelectorAll('li.b_algo').forEach(li => { const a = li.querySelector('h2 a'); if (!a) return; let url = (a.href || '').replace(/\/$/,''); if (!url.startsWith('http') || seen.has(url)) return; seen.add(url); if (sk.some(d => url.includes(d))) return; const hostname = url.replace(/^https?:\/\//,'').split('/')[0]; if (hostname.endsWith('.cn') || hostname.endsWith('.com.cn') || hostname.endsWith('.edu') || hostname.endsWith('.ac')) return; const title = (a.textContent||'').trim().substring(0,100); if (reCJK.test(title)) return; const s = li.querySelector('.b_caption p, .b_lineclamp2'); found.push({title, url, snippet:s?s.textContent.trim().substring(0,200):''}); }); return found.slice(0,m); }, max, sk); console.log(JSON.stringify(results)); } catch(e) { console.log('[]'); } await b.close(); })(); """ BING_SCRIPT_FILE = os.path.join(os.path.dirname(__file__), "_bing_search.js") NODE_MODULES = os.path.join(PROJECT_ROOT, "node_modules") async def search_bing(query: str, max_results: int = 10) -> List[Dict[str, str]]: try: with open(BING_SCRIPT_FILE, "w") as f: f.write(BING_SCRIPT) env = os.environ.copy() env["NODE_PATH"] = NODE_MODULES result = subprocess.run( [NODE_BIN, BING_SCRIPT_FILE, query, str(max_results)], capture_output=True, text=True, timeout=15, cwd=PROJECT_ROOT, env=env, ) if result.returncode != 0: logger.warning(f"Bing search failed: {result.stderr[:300]}") return [] for line in result.stdout.strip().split("\n"): line = line.strip() if line.startswith("["): return json.loads(line) return [] except subprocess.TimeoutExpired: logger.warning("Bing search timed out") return [] except (json.JSONDecodeError, Exception) as e: logger.warning(f"Bing search error: {e}") return [] mcp = FastMCP("trade-search", log_level="WARNING") @mcp.tool( name="web_search", description="Search the web for companies, buyers, or business information. Returns title, URL, and snippet for each result. Useful for finding potential customers, researching companies, or gathering market intelligence.", ) async def web_search(query: str, max_results: int = 10) -> str: results = await search_bing(query, max_results) if not results: return json.dumps({"results": [], "error": None}) return json.dumps({"results": results, "error": None}) def main(): asyncio.run(mcp.run_stdio_async()) if __name__ == "__main__": main()