a1276f38ad
新增文件: - 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>
118 lines
4.1 KiB
Python
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())
|