diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..0599a2b --- /dev/null +++ b/.env.example @@ -0,0 +1,15 @@ +# Telegram API 配置 +TELEGRAM_API_ID=your_api_id +TELEGRAM_API_HASH=your_api_hash +TELEGRAM_SESSION_PATH=~/telegram_sessions/funstat_bot +FUNSTAT_BOT_USERNAME=@openaiw_bot + +# 代理配置(如果服务器无法直接访问 Telegram,需要配置代理) +FUNSTAT_PROXY_TYPE=socks5 +FUNSTAT_PROXY_HOST=127.0.0.1 +FUNSTAT_PROXY_PORT=1080 + +# 服务器配置 +# 监听地址: 0.0.0.0 表示所有网络接口,127.0.0.1 表示仅本地访问 +FUNSTAT_HOST=0.0.0.0 +FUNSTAT_PORT=8091 diff --git a/DEPLOYMENT_INFO.md b/DEPLOYMENT_INFO.md new file mode 100644 index 0000000..73baba9 --- /dev/null +++ b/DEPLOYMENT_INFO.md @@ -0,0 +1,177 @@ +# Funstat MCP 部署信息 + +## 服务器信息 + +- **服务器 IP**: 172.16.74.159 +- **用户**: atai +- **部署路径**: /home/atai/funstat-mcp +- **端口**: 8091 (本地监听) + +## 部署状态 + +✅ **部署成功** - 2025-11-02 + +### 已安装组件 + +- Python 3.12.3 +- 虚拟环境: /home/atai/funstat-mcp/.venv +- MCP 服务器: v1.20.0 +- Telethon: v1.41.2 +- 代理: v2ray (SOCKS5 on 127.0.0.1:1080) + +### 配置文件 + +- **环境变量**: /home/atai/funstat-mcp/.env +- **Session 文件**: ~/telegram_sessions/funstat_bot.session +- **启动脚本**: /home/atai/funstat-mcp/core/start_server_with_env.sh + +## 服务管理 + +### 启动服务 + +```bash +ssh atai@172.16.74.159 +cd /home/atai/funstat-mcp/core +bash start_server_with_env.sh +``` + +### 停止服务 + +```bash +ssh atai@172.16.74.159 +pkill -f 'funstat.*server.py' +``` + +### 查看日志 + +```bash +ssh atai@172.16.74.159 +tail -f /tmp/funstat_sse.log +``` + +### 检查状态 + +```bash +ssh atai@172.16.74.159 +ps aux | grep python | grep server.py +netstat -tuln | grep 8091 # 或 ss -tuln | grep 8091 +``` + +## 服务端点 + +- **SSE 端点**: http://172.16.74.159:8091/sse +- **消息端点**: http://172.16.74.159:8091/messages +- **内部访问**: http://127.0.0.1:8091/sse + +✅ 服务监听 0.0.0.0:8091,可从任何网络接口访问。 + +## 环境变量 + +当前配置在 `/home/atai/funstat-mcp/.env`: + +```bash +# Telegram API 配置 +TELEGRAM_API_ID=24660516 +TELEGRAM_API_HASH=eae564578880a59c9963916ff1bbbd3a +TELEGRAM_SESSION_PATH=~/telegram_sessions/funstat_bot +FUNSTAT_BOT_USERNAME=@openaiw_bot + +# 代理配置 +FUNSTAT_PROXY_TYPE=socks5 +FUNSTAT_PROXY_HOST=127.0.0.1 +FUNSTAT_PROXY_PORT=1080 + +# 服务器配置 +FUNSTAT_HOST=0.0.0.0 +FUNSTAT_PORT=8091 +``` + +## 重新部署 + +从本地更新代码到服务器: + +```bash +cd /Users/hahaha/projects/funstat-mcp +bash deploy.sh +``` + +部署脚本会自动: +1. 打包项目文件 +2. 上传到服务器 +3. 停止旧服务 +4. 解压更新文件 +5. 安装依赖 + +部署后需要手动启动服务: + +```bash +ssh atai@172.16.74.159 +cd /home/atai/funstat-mcp/core +bash start_server_with_env.sh +``` + +## 故障排除 + +### 1. 服务无法启动 + +检查日志: +```bash +tail -50 /tmp/funstat_sse.log +``` + +### 2. 无法连接 Telegram + +检查代理是否运行: +```bash +ps aux | grep v2ray +netstat -tuln | grep 1080 +``` + +### 3. Session 文件锁定 + +强制停止并重启: +```bash +pkill -9 -f 'funstat.*server.py' +sleep 2 +cd /home/atai/funstat-mcp/core +bash start_server_with_env.sh +``` + +## 运行日志示例 + +成功启动的日志应该包含: + +``` +✅ 已连接到: KT超级数据 +✅ 当前账号: @xiaobai_80 +🚀 Funstat MCP Server 已启动 +🌐 启动 SSE 服务器: http://127.0.0.1:8091 +Uvicorn running on http://127.0.0.1:8091 +``` + +## 安全注意事项 + +1. ⚠️ .env 文件包含敏感信息,已设置权限保护 +2. ⚠️ Session 文件包含登录凭证,定期备份 +3. ⚠️ 服务监听 0.0.0.0:8091,可从任何网络访问,请注意安全 +4. ⚠️ 服务器需要使用 v2ray 代理访问 Telegram(端口 1080),确保代理服务稳定 +5. ⚠️ 必须使用 start_server_with_env.sh 启动脚本,才能正确加载环境变量和代理配置 +6. ⚠️ 建议在生产环境配置防火墙规则,限制访问来源IP + +## 更新历史 + +- **2025-11-02**: 初次部署成功 + - 安装所有依赖(包括 PySocks) + - 配置 v2ray 代理(socks5://127.0.0.1:1080) + - 创建带环境变量的启动脚本 + - 配置服务监听 0.0.0.0:8091,支持外部访问 + - 服务正常运行 + - ⚠️ 注意:服务器无法直接访问 Telegram,必须使用代理 + - ✅ 可从任何网络通过 http://172.16.74.159:8091 访问 + +## 联系信息 + +- 服务器: 172.16.74.159:22 +- 账号: atai +- Bot: @openaiw_bot +- 当前 Telegram 账号: @xiaobai_80 diff --git a/DEPLOYMENT_SUCCESS.md b/DEPLOYMENT_SUCCESS.md new file mode 100644 index 0000000..ffb97e6 --- /dev/null +++ b/DEPLOYMENT_SUCCESS.md @@ -0,0 +1,252 @@ +# Funstat MCP 部署成功报告 + +## ✅ 部署状态:成功 + +**部署时间**: 2025-11-02 +**服务器**: 172.16.74.159 (atai) +**服务进程**: PID 105552 + +--- + +## 🌐 访问信息 + +### 外部访问 +- **SSE 端点**: http://172.16.74.159:8091/sse +- **消息端点**: http://172.16.74.159:8091/messages + +### 内部访问(服务器内) +- http://127.0.0.1:8091/sse +- http://127.0.0.1:8091/messages + +### 监听配置 +- **监听地址**: 0.0.0.0 (所有网络接口) +- **监听端口**: 8091 +- **协议**: HTTP + SSE + +--- + +## ✅ 服务状态 + +```bash +# 进程状态 +PID: 105552 +运行中: ✅ +内存使用: 78304 KB + +# 网络状态 +端口: 0.0.0.0:8091 LISTEN +防火墙: inactive (无限制) + +# Telegram 连接 +状态: ✅ 已连接 +Bot: @openaiw_bot (KT超级数据) +账号: @xiaobai_80 (ID: 7363537082) +代理: socks5://127.0.0.1:1080 +``` + +--- + +## 📋 部署清单 + +### 1. 系统环境 +- [x] Ubuntu 24.04.3 LTS +- [x] Python 3.12.3 +- [x] v2ray 代理运行中 (端口 1080) + +### 2. 项目部署 +- [x] 代码部署到 /home/atai/funstat-mcp +- [x] 虚拟环境创建 (.venv) +- [x] 依赖包安装完成 (mcp, telethon, starlette, uvicorn, PySocks, etc.) + +### 3. 配置文件 +- [x] .env 环境变量配置 +- [x] Session 文件 (~/telegram_sessions/funstat_bot.session) +- [x] 启动脚本 (start_server_with_env.sh) + +### 4. 服务配置 +- [x] 监听地址: 0.0.0.0:8091 +- [x] 代理配置: socks5://127.0.0.1:1080 +- [x] 自动重启脚本 + +--- + +## 🔧 管理命令 + +### 启动服务 +```bash +ssh atai@172.16.74.159 +cd /home/atai/funstat-mcp/core +bash start_server_with_env.sh +``` + +### 停止服务 +```bash +ssh atai@172.16.74.159 +pkill -f 'funstat.*server.py' +``` + +### 查看日志 +```bash +ssh atai@172.16.74.159 +tail -f /tmp/funstat_sse.log +``` + +### 检查状态 +```bash +ssh atai@172.16.74.159 +ps aux | grep 'python.*server.py' | grep -v grep +ss -tuln | grep 8091 +``` + +### 测试访问 +```bash +# 从服务器内部测试 +ssh atai@172.16.74.159 +curl -s -o /dev/null -w '%{http_code}\n' http://172.16.74.159:8091/sse + +# 从本地测试(如果网络可达) +curl -s -o /dev/null -w '%{http_code}\n' http://172.16.74.159:8091/sse +``` + +--- + +## 🔄 重新部署 + +### 方法 1: 使用部署脚本(本地执行) +```bash +cd /Users/hahaha/projects/funstat-mcp +bash deploy.sh +``` + +然后登录服务器启动: +```bash +ssh atai@172.16.74.159 +cd /home/atai/funstat-mcp/core +bash start_server_with_env.sh +``` + +### 方法 2: 手动部署 +```bash +# 1. 打包代码 +cd /Users/hahaha/projects/funstat-mcp +tar --exclude='.git' --exclude='.venv' -czf /tmp/funstat-mcp.tar.gz . + +# 2. 上传到服务器 +scp /tmp/funstat-mcp.tar.gz atai@172.16.74.159:/tmp/ + +# 3. 在服务器上解压并重启 +ssh atai@172.16.74.159 +cd /home/atai/funstat-mcp +tar -xzf /tmp/funstat-mcp.tar.gz +cd core +bash start_server_with_env.sh +``` + +--- + +## ⚠️ 重要说明 + +### 网络要求 +- ⚠️ **服务器无法直接访问 Telegram**,必须通过 v2ray 代理(端口 1080) +- ✅ v2ray 代理已配置并运行正常 +- ✅ 服务已配置使用 socks5://127.0.0.1:1080 代理 + +### 代理依赖 +如果代理服务停止,Telegram 连接将失败。检查代理状态: +```bash +ssh atai@172.16.74.159 +ps aux | grep v2ray +ss -tuln | grep 1080 +``` + +### Session 文件 +- 路径: ~/telegram_sessions/funstat_bot.session +- 大小: 72KB +- 权限: 600 (仅所有者可读写) +- ⚠️ 包含登录凭证,务必保管好 + +### 防火墙 +- 当前状态: inactive (无防火墙限制) +- 端口 8091 对所有网络开放 +- 如需限制访问,可配置 ufw 规则 + +--- + +## 📊 访问日志示例 + +服务器日志显示已有外部访问: +``` +INFO: 172.16.72.87:61462 - "GET /sse HTTP/1.1" 406 Not Acceptable +INFO: 172.16.74.159:58984 - "GET /sse HTTP/1.1" 406 Not Acceptable +INFO: 172.16.72.87:61532 - "GET /sse HTTP/1.1" 200 OK +``` + +- 406 响应: 正常,表示客户端未发送正确的 Accept 头 +- 200 响应: 成功建立 SSE 连接 + +--- + +## 🛡️ 安全建议 + +1. ✅ Session 文件已设置权限保护 (600) +2. ✅ .env 文件包含敏感信息,不要提交到 Git +3. ⚠️ 服务监听 0.0.0.0,建议配置防火墙限制访问 IP +4. ⚠️ 定期备份 session 文件 +5. ⚠️ 确保 v2ray 代理服务稳定运行 + +### 推荐的防火墙配置(可选) +```bash +sudo ufw allow from /32 to any port 8091 +sudo ufw enable +``` + +--- + +## 📞 技术支持 + +如遇问题,按以下顺序排查: + +1. **服务未启动** + ```bash + cd /home/atai/funstat-mcp/core + bash start_server_with_env.sh + ``` + +2. **无法连接 Telegram** + - 检查 v2ray 代理: `ps aux | grep v2ray` + - 检查代理端口: `ss -tuln | grep 1080` + - 查看服务日志: `tail -50 /tmp/funstat_sse.log` + +3. **Session 文件锁定** + ```bash + pkill -9 python3 + sleep 2 + cd /home/atai/funstat-mcp/core + bash start_server_with_env.sh + ``` + +4. **依赖包缺失** + ```bash + cd /home/atai/funstat-mcp + source .venv/bin/activate + pip install -r requirements.txt + ``` + +--- + +## ✅ 验证清单 + +部署完成后,确认以下项目: + +- [x] 服务进程正在运行 +- [x] 端口 8091 正在监听 +- [x] Telegram 连接成功 +- [x] SSE 端点响应正常 +- [x] 日志文件正常记录 +- [x] 可从外部访问(如果网络可达) + +--- + +**部署完成时间**: 2025-11-02 +**状态**: ✅ 成功运行 +**下次维护**: 定期检查日志和代理状态 diff --git a/README.md b/README.md index b9d16b2..6ae09eb 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ funstat_mcp_package/ ## 🎯 核心功能 -### MCP 工具列表 (8个) +### MCP 工具列表 (9个) | 工具名 | 功能 | 对应命令 | |--------|------|---------| @@ -61,6 +61,7 @@ funstat_mcp_package/ | `funstat_text` | 搜索消息内容 | `/text [关键词]` | | `funstat_human` | 搜索用户 | `/human [关键词]` | | `funstat_user_info` | 查询用户详情 | `/user_info [username]` | +| `funstat_user_messages` | 获取用户聊天记录(自动翻页) | `/user_info [username] ➜ Messages ➜ All` | ### 协议支持 diff --git a/core/server.py b/core/server.py index a180c5b..a62b056 100644 --- a/core/server.py +++ b/core/server.py @@ -25,6 +25,8 @@ from mcp.types import ( ) from pydantic import AnyUrl from telethon import TelegramClient +from telethon.errors import FloodWaitError +from telethon.tl.functions.messages import GetBotCallbackAnswerRequest from telethon.tl.types import Message # 配置日志 @@ -56,6 +58,40 @@ RATE_LIMIT_WINDOW = 1.0 # 1秒时间窗口 # 缓存配置 CACHE_TTL = 3600 # 缓存1小时 +# 按钮文本转换表,用于将常见的变体字符标准化为 ASCII +BUTTON_TEXT_TRANSLATIONS = str.maketrans({ + 'ƒ': 'f', + 'Μ': 'M', + 'τ': 't', + 'ѕ': 's', + 'η': 'n', + 'Ғ': 'F', + 'α': 'a', + 'ο': 'o', + 'ᴜ': 'u', + 'о': 'o', + 'е': 'e', + 'с': 'c', + '℮': 'e', + 'Τ': 'T', + 'ρ': 'p', + 'Δ': 'D', + 'χ': 'x', + 'β': 'b', + 'λ': 'l', + 'γ': 'y', + 'Ν': 'N', + 'μ': 'm', + 'ψ': 'y', + 'Α': 'A', + 'Ρ': 'P', + 'С': 'C', + 'ё': 'e', + 'ł': 'l', + 'Ł': 'L', + 'ց': 'g', +}) + class RateLimiter: """速率限制器""" @@ -136,6 +172,123 @@ class FunstatMCPServer: self.server.list_tools()(self.list_tools) self.server.call_tool()(self.call_tool) + def _normalize_button_text(self, text: str) -> str: + """标准化按钮文本,消除不同字符集的影响""" + return text.translate(BUTTON_TEXT_TRANSLATIONS) + + async def _press_button(self, message: Message, keyword: str) -> Message: + """在消息中查找包含关键字的按钮并触发""" + if not message.buttons: + raise ValueError(f"消息中缺少可用按钮,无法执行 {keyword} 操作") + + target_button = None + normalized_keyword = keyword.lower() + + for row in message.buttons: + for button in row: + normalized_text = self._normalize_button_text(button.text).lower() + if normalized_keyword in normalized_text: + target_button = button + break + if target_button: + break + + if not target_button: + available = [ + self._normalize_button_text(button.text) + for row in message.buttons + for button in row + ] + raise ValueError( + f"未找到包含关键字 '{keyword}' 的按钮。可用按钮: {available}" + ) + + await self.rate_limiter.acquire() + + try: + await self.client( + GetBotCallbackAnswerRequest( + peer=self.bot_entity, + msg_id=message.id, + data=target_button.data, + ) + ) + except FloodWaitError as exc: + wait_seconds = exc.seconds + 1 + logger.warning("触发 Telegram FloodWait,需要等待 %s 秒", wait_seconds) + await asyncio.sleep(wait_seconds) + await self.client( + GetBotCallbackAnswerRequest( + peer=self.bot_entity, + msg_id=message.id, + data=target_button.data, + ) + ) + + await asyncio.sleep(1.2) + refreshed = await self.client.get_messages(self.bot_entity, ids=message.id) + if not refreshed: + raise RuntimeError("回调执行后未能获取最新消息内容") + return refreshed + + def _extract_total_pages(self, message: Message) -> Optional[int]: + """从按钮中提取总页数(如果提供了跳页按钮)""" + if not message.buttons: + return None + + total_pages = None + for row in message.buttons: + for button in row: + if '⏭' in button.text: + normalized = self._normalize_button_text(button.text) + digits = ''.join(ch for ch in normalized if ch.isdigit()) + if digits: + try: + total_pages = int(digits) + except ValueError: + continue + return total_pages + + + async def send_command_and_wait_message( + self, + command: str, + timeout: int = 10, + ) -> Message: + """发送命令并返回原始消息对象(包含按钮等信息)""" + if not self.client or not self.bot_entity: + raise RuntimeError("Telegram 客户端尚未初始化") + + await self.rate_limiter.acquire() + + last_message_id = 0 + async for message in self.client.iter_messages(self.bot_entity, limit=1): + last_message_id = message.id + break + + logger.info("📤 发送命令(原始消息模式): %s", command) + await self.client.send_message(self.bot_entity, command) + + await asyncio.sleep(1.5) + start_time = time.time() + + while time.time() - start_time < timeout: + async for message in self.client.iter_messages(self.bot_entity, limit=5): + if message.id <= last_message_id: + continue + if message.out: + continue + if message.text or message.buttons: + logger.info( + "✅ 收到原始响应 (ID: %s, 文本长度: %s)", + message.id, + len(message.text or ""), + ) + return message + await asyncio.sleep(0.5) + + raise TimeoutError(f"等待 BOT 响应超时 ({timeout}秒)") + async def initialize(self): """初始化 Telegram 客户端""" logger.info("初始化 Telegram 客户端...") @@ -246,6 +399,99 @@ class FunstatMCPServer: raise TimeoutError(f"等待 BOT 响应超时 ({timeout}秒)") + async def fetch_user_messages( + self, + identifier: str, + max_pages: Optional[int] = None + ) -> str: + """获取指定用户的历史消息,支持自动翻页""" + if not identifier or not identifier.strip(): + raise ValueError("用户标识不能为空") + + identifier = identifier.strip() + display_identifier = identifier + + if identifier.startswith("/"): + command = identifier + else: + if not identifier.startswith("@") and not identifier.replace("+", "").isdigit(): + identifier = f"@{identifier}" + display_identifier = identifier + command = f"/user_info {identifier}" + + logger.info("开始获取用户消息: %s", display_identifier) + + base_message = await self.send_command_and_wait_message(command, timeout=15) + + message_stage = await self._press_button(base_message, "messages") + all_stage = await self._press_button(message_stage, "all") + + collected_pages: List[str] = [] + seen_texts: set[str] = set() + current_message = all_stage + current_page = 1 + total_pages = self._extract_total_pages(current_message) + + if max_pages is not None and max_pages <= 0: + raise ValueError("max_pages 必须大于 0") + + while True: + page_text = current_message.text or "" + normalized_text = page_text.strip() + + if normalized_text and normalized_text not in seen_texts: + header_parts = [f"第 {current_page} 页"] + if total_pages: + header_parts[-1] += f"/{total_pages}" + header_parts.append(f"用户: {display_identifier}") + collected_pages.append( + "\n".join(header_parts + ["", page_text.strip()]) + ) + seen_texts.add(normalized_text) + + if max_pages and current_page >= max_pages: + logger.info("达到 max_pages 限制,停止翻页") + break + + next_button = None + if current_message.buttons: + for row in current_message.buttons: + for button in row: + if "➡" in button.text: + next_button = button + break + if next_button: + break + + if not next_button: + break + + logger.info("翻到第 %s 页 (目标按钮: %s)", current_page + 1, next_button.text) + + try: + current_message = await self._press_button(current_message, "➡") + except ValueError: + logger.warning("未能找到下一页按钮,提前结束翻页") + break + + # 如果返回的内容与上一页一致,则终止 + if (current_message.text or "").strip() in seen_texts: + logger.info("检测到重复页面内容,结束翻页") + break + + current_page += 1 + + if not collected_pages: + return f"未找到 {display_identifier} 的消息记录。" + + summary_lines = [ + f"共收集 {len(collected_pages)} 页消息" + + (f"(存在 {total_pages} 页)" if total_pages else ""), + "" + ] + + return "\n\n".join(summary_lines + collected_pages) + async def list_tools(self) -> List[Tool]: """列出所有可用工具""" return [ @@ -318,6 +564,25 @@ class FunstatMCPServer: "required": ["identifier"] } ), + Tool( + name="funstat_user_messages", + description="获取指定用户的历史消息列表,并自动翻页汇总", + inputSchema={ + "type": "object", + "properties": { + "identifier": { + "type": "string", + "description": "用户标识: 用户名(@username) 或用户ID" + }, + "max_pages": { + "type": "integer", + "minimum": 1, + "description": "可选,限制抓取的最大页数" + } + }, + "required": ["identifier"] + } + ), Tool( name="funstat_balance", description="查询当前账号的积分余额和使用统计", @@ -381,6 +646,12 @@ class FunstatMCPServer: response = await self.send_command_and_wait(f"/user_info {identifier}") return [TextContent(type="text", text=response)] + elif name == "funstat_user_messages": + identifier = arguments["identifier"] + max_pages = arguments.get("max_pages") + response = await self.fetch_user_messages(identifier, max_pages=max_pages) + return [TextContent(type="text", text=response)] + elif name == "funstat_balance": response = await self.send_command_and_wait("/balance") return [TextContent(type="text", text=response)] diff --git a/core/start_server.sh b/core/start_server.sh new file mode 100755 index 0000000..a2b3ceb --- /dev/null +++ b/core/start_server.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# Funstat MCP 服务器启动脚本(适配服务器环境) + +set -e + +cd "$(dirname "$0")" + +# 停止旧实例 +echo "🛑 停止旧服务器..." +pkill -f "funstat.*server.py" 2>/dev/null || true +sleep 2 + +# 确保 session 文件没有被锁定 +SESSION_FILE=~/telegram_sessions/funstat_bot.session +if [ -f "$SESSION_FILE" ]; then + if lsof "$SESSION_FILE" 2>/dev/null; then + echo "⚠️ Session 文件被占用,强制终止..." + pkill -9 -f "funstat.*server.py" || true + sleep 2 + fi +else + echo "❌ Session 文件不存在: $SESSION_FILE" + echo "请先上传 session 文件!" + exit 1 +fi + +# 激活虚拟环境 +source ../.venv/bin/activate + +# 启动新服务器(后台运行) +echo "🚀 启动新服务器..." +nohup python3 server.py > /tmp/funstat_sse.log 2>&1 & +SERVER_PID=$! + +# 等待启动 +sleep 3 + +# 验证启动 +if ps -p $SERVER_PID > /dev/null 2>&1; then + echo "✅ 服务器已启动 (PID: $SERVER_PID)" + echo "📡 SSE 端点: http://127.0.0.1:8091/sse" + echo "📋 日志文件: /tmp/funstat_sse.log" + + # 测试端点 + echo "" + echo "🧪 测试端点..." + if curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8091/sse | grep -q "200"; then + echo "✅ GET /sse 测试通过" + else + echo "❌ GET /sse 测试失败" + fi + + echo "" + echo "📊 服务器状态:" + echo " 进程ID: $SERVER_PID" + echo " 监听地址: http://127.0.0.1:8091" + echo " 日志: tail -f /tmp/funstat_sse.log" + echo "" + echo "停止服务: pkill -f 'funstat.*server.py'" +else + echo "❌ 服务器启动失败!" + echo "查看日志: tail -50 /tmp/funstat_sse.log" + exit 1 +fi diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..ea66d0a --- /dev/null +++ b/deploy.sh @@ -0,0 +1,77 @@ +#!/bin/bash +# Funstat MCP 服务器部署脚本 + +set -e + +SERVER_IP="172.16.74.159" +SERVER_USER="atai" +SERVER_PATH="/home/atai/funstat-mcp" +LOCAL_PATH="/Users/hahaha/projects/funstat-mcp" + +echo "🚀 开始部署 Funstat MCP 到服务器..." + +# 1. 打包项目文件(排除不需要的文件) +echo "📦 打包项目文件..." +cd "$LOCAL_PATH" +tar --exclude='.git' \ + --exclude='.venv' \ + --exclude='__pycache__' \ + --exclude='*.pyc' \ + --exclude='.DS_Store' \ + --exclude='customer_data' \ + -czf /tmp/funstat-mcp.tar.gz . + +# 2. 上传到服务器 +echo "⬆️ 上传文件到服务器..." +sshpass -p "wengewudi666808" scp /tmp/funstat-mcp.tar.gz ${SERVER_USER}@${SERVER_IP}:/tmp/ + +# 3. 在服务器上部署 +echo "🔧 在服务器上部署..." +sshpass -p "wengewudi666808" ssh ${SERVER_USER}@${SERVER_IP} << 'ENDSSH' +set -e + +# 创建部署目录 +mkdir -p /home/atai/funstat-mcp +cd /home/atai/funstat-mcp + +# 停止旧服务 +echo "🛑 停止旧服务..." +pkill -f "funstat.*server.py" 2>/dev/null || true +sleep 2 + +# 解压新文件 +echo "📂 解压新文件..." +tar -xzf /tmp/funstat-mcp.tar.gz -C /home/atai/funstat-mcp +rm /tmp/funstat-mcp.tar.gz + +# 创建虚拟环境(如果不存在) +if [ ! -d ".venv" ]; then + echo "🔨 创建虚拟环境..." + python3 -m venv .venv +fi + +# 激活虚拟环境并安装依赖 +echo "📥 安装依赖..." +source .venv/bin/activate +pip install --upgrade pip +pip install -r requirements.txt + +# 创建 session 目录 +mkdir -p ~/telegram_sessions + +# 检查 session 文件 +if [ ! -f ~/telegram_sessions/funstat_bot.session ]; then + echo "⚠️ 警告: Session 文件不存在" + echo "请确保已经上传 session 文件到 ~/telegram_sessions/funstat_bot.session" +fi + +echo "✅ 部署完成!" +echo "启动服务: cd /home/atai/funstat-mcp/core && bash start_server.sh" +ENDSSH + +echo "" +echo "✅ 部署完成!" +echo "下一步:" +echo "1. 确保 session 文件已上传到服务器: ~/telegram_sessions/funstat_bot.session" +echo "2. SSH 到服务器: ssh atai@172.16.74.159" +echo "3. 启动服务: cd /home/atai/funstat-mcp/core && bash start_server.sh" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..dc499ca --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +mcp>=1.12.0 +telethon>=1.34.0 +starlette>=0.41.0 +uvicorn>=0.29.0 +httpx>=0.28.0 +pydantic>=2.0.0 +python-dotenv>=1.0.0 +PySocks>=1.7.1 diff --git a/test_connection.sh b/test_connection.sh new file mode 100755 index 0000000..31b7d9e --- /dev/null +++ b/test_connection.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# 测试 Funstat MCP 服务器连接 + +SERVER_HOST="${1:-172.16.74.159}" +SERVER_PORT="${2:-8091}" + +echo "测试 Funstat MCP 服务器连接..." +echo "服务器: $SERVER_HOST:$SERVER_PORT" +echo "" + +# 测试 1: TCP 连接 +echo "1. 测试 TCP 连接..." +if timeout 3 bash -c "cat < /dev/null > /dev/tcp/$SERVER_HOST/$SERVER_PORT" 2>/dev/null; then + echo " ✅ TCP 连接成功" +else + echo " ❌ TCP 连接失败" + exit 1 +fi + +# 测试 2: HTTP GET 请求 +echo "" +echo "2. 测试 HTTP GET 请求..." +HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 5 http://$SERVER_HOST:$SERVER_PORT/sse) +if [ "$HTTP_CODE" = "406" ] || [ "$HTTP_CODE" = "200" ]; then + echo " ✅ HTTP 响应: $HTTP_CODE (服务器正常)" +else + echo " ❌ HTTP 响应: $HTTP_CODE" +fi + +# 测试 3: SSE 端点 +echo "" +echo "3. 测试 SSE 端点(Accept: text/event-stream)..." +RESPONSE=$(timeout 3 curl -s -H "Accept: text/event-stream" http://$SERVER_HOST:$SERVER_PORT/sse 2>&1 | head -1) +if [ -n "$RESPONSE" ]; then + echo " ✅ SSE 端点响应: $RESPONSE" +else + echo " ⚠️ SSE 端点正在等待连接(正常)" +fi + +echo "" +echo "✅ 服务器 $SERVER_HOST:$SERVER_PORT 可以正常访问!" +echo "" +echo "访问地址:" +echo " - SSE 端点: http://$SERVER_HOST:$SERVER_PORT/sse" +echo " - 消息端点: http://$SERVER_HOST:$SERVER_PORT/messages"