Files
Nexus/mcp/firefox_server.py
T
Your Name a1276f38ad feat: 一键安装脚本 + PHP完全移除 + config.php→config.json
新增文件:
- deploy/install.sh: 7步一键安装(自动检测宝塔/标准模式)
- deploy/upgrade.sh: git pull + pip install + restart + 健康检查
- deploy/uninstall.sh: 停服务+删配置, 可选--purge删部署目录
- docs/install-guide.md: 完整中文安装教程(宝塔+手动)

核心改动:
- install.py: 宝塔Supervisor路径自适应, init-db返回bt_panel标记,
  config.php→config.json, 移除PHP锁文件, 生成Fernet加密密钥
- install.html: Step 5根据btPanel显示不同Supervisor/Nginx/SSL指引
- crypto.py: 移除PHP AES-CBC兼容层, 纯Fernet加密
- 删除 web/install.php (1192行PHP, 功能已完全迁移到Python)

PHP清理:
- Nginx配置: config\.php→config\.json deny规则
- config.py/main.py/migrations.py: install.php注释→install wizard
- CLAUDE.md/AGENTS.md: config.php→config.json, 移除PHP引用
- firefox_server.py: login.php→login.html默认URL

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-25 08:23:37 +08:00

118 lines
4.1 KiB
Python

#!/usr/bin/env python3
"""Firefox Browser MCP — 控制 Firefox 进行 Web 测试"""
import json, time, base64, asyncio
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.firefox.service import Service
from mcp.server import Server
from mcp.server.stdio import stdio_server
server = Server("firefox-mcp")
driver = None
def _get_driver():
global driver
if driver is None:
opts = Options()
opts.add_argument("--headless")
opts.add_argument("--no-sandbox")
opts.add_argument("--disable-dev-shm-usage")
driver = webdriver.Firefox(options=opts)
driver.set_page_load_timeout(30)
return driver
# ================================================================
# T1: 导航
# ================================================================
@server.tool()
async def firefox_navigate(url: str = "https://192.168.124.10/app/login.html"):
"""Firefox 打开指定 URL,返回页面标题和 URL"""
try:
d = _get_driver()
d.get(url)
return f"标题: {d.title}\nURL: {d.current_url}"
except Exception as e:
return f"导航失败: {e}"
# ================================================================
# T2: 截图
# ================================================================
@server.tool()
async def firefox_screenshot():
"""截取当前页面截图,返回 base64 PNG"""
try:
d = _get_driver()
png = d.get_screenshot_as_base64()
return f"截图大小: {len(png)} bytes (base64). 可用工具查看。\n前100字符: {png[:100]}..."
except Exception as e:
return f"截图失败: {e}"
# ================================================================
# T3: 获取页面文本
# ================================================================
@server.tool()
async def firefox_get_text():
"""获取当前页面可见文本内容(用于检查错误信息)"""
try:
d = _get_driver()
body = d.find_element(By.TAG_NAME, "body")
text = body.text[:3000]
return text if text else "(空页面)"
except Exception as e:
return str(e)
# ================================================================
# T4: 填充输入框并提交
# ================================================================
@server.tool()
async def firefox_fill(selector: str = "input[name='username']", value: str = "admin"):
"""在指定 CSS selector 的输入框中填入值"""
try:
d = _get_driver()
el = d.find_element(By.CSS_SELECTOR, selector)
el.clear()
el.send_keys(value)
return f"已填入 {selector} = {value}"
except Exception as e:
return str(e)
# ================================================================
# T5: 点击
# ================================================================
@server.tool()
async def firefox_click(selector: str = "button[type='submit']"):
"""点击指定 CSS selector 的元素"""
try:
d = _get_driver()
el = d.find_element(By.CSS_SELECTOR, selector)
el.click()
time.sleep(2)
return f"已点击 {selector}。当前页面: {d.title} ({d.current_url})"
except Exception as e:
return str(e)
# ================================================================
# T6: 控制台日志
# ================================================================
@server.tool()
async def firefox_console():
"""获取浏览器控制台日志"""
try:
d = _get_driver()
logs = d.get_log("browser")
entries = [f"[{e['level']}] {e['message'][:200]}" for e in logs[-20:]]
return "\n".join(entries) if entries else "(无日志)"
except Exception as e:
return str(e)
# ================================================================
# Main
# ================================================================
async def main():
async with stdio_server() as (read, write):
await server.run(read, write, server.create_initialization_options())
if __name__ == "__main__":
asyncio.run(main())