# VPS MCP **Repository Path**: jimonik/vpsmcp ## Basic Information - **Project Name**: VPS MCP - **Description**: vps 的聊天工具 mcp,不要让 ai 滥用 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2026-10-15 - **Last Updated**: 2026-10-31 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # vpsmcp 面向 VPS Desktop 的消息 MCP Server,通过 VPS 官方 HTTP IM 接口读取会话、历史及增量事件,并在用户明确要求时发送消息。 ## MCP 工具 | 工具 | 功能 | | --- | --- | | `check_connection` | 检查 Token、服务连通性和当前增量 cursor | | `list_recent_chats` | 列出最近会话、最后消息及未读数 | | `search_chats` | 按名称、成员或 channel_id 搜索会话 | | `sync_vps_data` | 用户主动同步会话、群成员、组织用户和近期消息到本地索引 | | `search_cached_people` | 本地搜索群成员/组织用户,取得 @ 所需 user_id | | `search_cached_messages` | 本地搜索消息并取得引用回复所需 message_id | | `get_chat_history` | 分页读取会话历史 | | `get_new_messages` | 按持久 cursor 获取新的消息事件 | | `set_message_cursor` | 重设增量 cursor | | `enable_monitoring` | 将指定群/私聊的新消息投递到固定 Codex session,可筛选群成员 | | `list_monitoring` | 查询基线建立、监听、停止或错误状态及退出检测方式 | | `disable_monitoring` | 停止监听和后续投递 | | `create_direct_chat` | 创建或取得指定用户的私聊 | | `send_message` | 发送文本或已上传的附件,可引用消息 | | `upload_file` | 上传本地文件并返回附件对象,供后续发送复用 | | `send_file` | 上传本地文件并发送到会话,可附带文字和引用 | | `mark_message_read` | 标记消息已读 | | `revoke_message` | 撤回本人发送的消息 | ## 安装和配置 ```bash cd /Users/jimo/PycharmProjects/vpsmcp /opt/homebrew/Caskroom/miniconda/base/envs/py312/bin/python3 -m venv .venv .venv/bin/python -m pip install -r requirements.txt ``` 需要 Python 3.12+;本机 `/usr/bin/python3` 是 3.9,不要用它启动此项目。 复制 `examples/mcp-config.example.json` 中的配置到 MCP 客户端即可。默认无需手工配置 Token:服务会自动读取 VPS Desktop 为本机 Agent 签发的合法 `app_key`: ```text ~/Library/Application Support/vertu-im-desktop/personal-opencode/home/.vps-work/vps-service.json ``` 该凭据也是 VPS Desktop 自带 `vps-work im` 命令使用的 Bearer 凭据。MCP 只在内存中使用,不返回密钥值,也不会复制到项目目录。 可选环境变量: - `VPS_TOKEN`:显式凭据;设置后优先于桌面端自动发现; - `VPS_BASE_URL`:可选,默认 `https://vps-service.vertu.cn`; - `VPS_DESKTOP_CREDENTIAL_FILE`:可选,明确指定桌面端 Agent 凭据文件; - `VPS_DESKTOP_AGENT_HOME`:可选,覆盖桌面端本机 Agent HOME; - `VPS_MCP_STATE`:可选,增量 cursor 状态文件路径; - `VPS_MCP_MONITOR_STATE`:可选,监听恢复检查点文件路径; - `VPS_MCP_CHANNEL_CACHE`:可选,会话目录缓存文件路径; - `VPS_MCP_CHANNEL_CACHE_TTL_SECONDS`:可选,会话目录缓存秒数,默认 300; - `VPS_MCP_DATA_CACHE`:可选,主动同步的持久数据索引路径;该索引不会自动过期或后台同步; - `VPS_TIMEOUT_SECONDS`:可选,HTTP 超时秒数,默认 30。 - `VPS_CODEX_BIN`:可选,Codex 可执行文件路径;默认从 PATH 查找 `codex`。 - `VPS_UPLOAD_TIMEOUT_SECONDS`:可选,OSS 文件直传超时秒数,默认 300。 项目不会解密 VPS Desktop 的人工登录会话,也不会读取账号密码。自动凭据必须以 `vta_` 开头、未过期且文件权限为 `0600`,否则拒绝使用。 连接诊断: ```bash .venv/bin/python mcp_server.py --check ``` stdio 启动: ```bash .venv/bin/python mcp_server.py ``` Streamable HTTP 启动: ```bash .venv/bin/python mcp_server.py --transport streamable-http --port 8767 ``` ## 新消息语义 `get_new_messages` 调用 `/v1/im/sync`,成功后将 `next_cursor` 原子写入 `.vps-state/cursor.json`。状态目录权限为 `0700`,文件权限为 `0600`。 先写入磁盘,再更新内存中的 cursor;保存失败时保持旧游标,下次可以重新领取。手动设置 cursor 也遵循这一顺序。服务返回 `has_more` 时优先使用它,否则根据事件数量判断是否还有下一页。 首次 cursor 为 `0`,因此服务可能返回当前仍保留的历史事件。如果只想接收建立基线后的消息,可先调用一次 `get_new_messages(limit=500)` 直到 `has_more=false`,丢弃返回内容,再开始正常领取。也可以把已知服务 cursor 传给 `set_message_cursor`。 ## 发送文件 先用 `search_chats` 确认收件人并取得 `channel_id`,再调用 `send_file`。参数示例: ```json { "channel_id": "从搜索结果取得的会话 ID", "file_path": "/绝对路径/交接说明.md", "text": "请参考附件中的改动与验证说明。", "client_message_id": "handoff-20261020-001" } ``` `file_path` 是运行 MCP 的本机上的普通文件路径,支持中文文件名和 `~`。`mime_type` 可省略,由文件名推断;未知类型使用 `application/octet-stream`。`quote_message_id` 可选,用于引用会话中的消息。 文件按以下接口顺序发送,协议依据 VPS Desktop `0.2.98` 的客户端实现核对: 1. `POST /v1/im/files`,`action=init`,取得上传地址、请求头和文件引用。 2. 按服务返回的地址和请求头,用 `PUT` 流式直传 OSS。 3. `POST /v1/im/files`,`action=complete`,确认得到完整附件。 4. `POST /v1/im/channels/{channel_id}/messages`,将附件放入 `attachments`。 不会将整个文件转为 Base64 放进 JSON;文件大小限制以服务端返回值为准。普通文件使用单次直传,当前未实现大视频的分片续传。OSS 请求与 VPS API 的鉴权请求分开构造,且不自动跟随重定向。上传错误只返回阶段和 HTTP 状态,不输出带签名的上传地址或 OSS 错误正文。 成功时 `send_file` 返回 `ok=true` 和服务端消息结果。上传失败会报错并停止,不创建消息;上传完成但消息接口报错时返回: ```json { "ok": false, "status": "message_unconfirmed", "client_message_id": "原始消息 ID", "error": "具体错误", "retry": { "tool": "send_message", "arguments": { "channel_id": "原始会话 ID", "text": "原始文字", "client_message_id": "原始消息 ID", "quote_message_id": null, "attachments": ["实际结果中此处是完整附件对象"] } } } ``` 消息请求失败并不代表服务端一定没有收到。先检查会话记录;需要续发时,将实际返回的 `retry.arguments` 原样交给 `send_message`,复用附件和原始 `client_message_id`。不要重新调用 `send_file` 生成另一条消息。服务端负责处理消息 ID 的去重,客户端不自动重试发消息。 需要先上传、后发送或将同一附件发到多个明确指定的会话时,可先调用 `upload_file(file_path=...)`,再将其返回的 `attachment` 放入 `send_message` 的 `attachments` 数组。纯附件消息允许 `text=""`,文字和附件不能同时为空。 新增工具需要重新启动 MCP 进程、刷新客户端工具列表后才会出现。现有 stdio/HTTP 启动方式及 MCP 配置无需修改。 ## 安全边界 - 仅用于当前用户有权访问的 VPS 账号与会话。 - `send_message`、`upload_file`、`send_file`、`mark_message_read`、`revoke_message` 会修改服务端状态;上传和发送只在用户明确要求时执行。 - 服务端不记录 Token,也不会在 MCP 工具结果中返回 Token。 - cursor 和监听状态文件不保存聊天正文;会话缓存会保存搜索及最近会话所需的群名、成员、未读数和最后一条消息,按账号隔离且使用 `0600` 权限,默认5分钟刷新。 - `sync_vps_data` 只在用户主动调用时联网,并将会话、成员、组织用户和所选会话近期消息保存在账号隔离的 `0600` 本地索引。普通搜索优先复用该索引,不会因过期自动全量请求。若群、人员或引用消息一直找不到或容易出错,应提示用户“请同步 VPS 信息数据”。 - 即使尚未主动全量同步,首次 `search_chats` 联网取得的会话目录和首次 `get_chat_history` 读取的最新一页消息也会写入该持久索引;后续查同一群或引用消息直接走本地。主动同步用于补齐完整群成员、组织用户及指定范围的近期消息。 ## 验证 ```bash .venv/bin/python -m compileall -q vpsmcp mcp_server.py .venv/bin/python -m unittest discover -s tests -v ``` 测试使用本地 HTTP 模拟服务和临时文件,覆盖真实字节上传、中文文件名、鉴权隔离、上传与确认失败、重定向拒绝、消息续发以及 cursor 落盘失败。测试不会向真实联系人发消息或上传文件;真实 VPS 上传权限和发送结果需在有明确收件人及文件的任务中验证。 ## 固定 Codex session 消息监听(2026-10-27) 开启前先取得准确的 Codex thread UUID,用 `search_chats` 确认 VPS 会话。 AI 不得猜测 thread ID、进程 PID 或收件人。用户仅询问能力时不能开启监听。 工具示例(占位符必须替换为真实值): ```json { "thread_id": "已有 Codex session 的 UUID", "channel_id": "目标 VPS 会话 ID", "sender_user_id": 123, "owner_pid": 45678 } ``` `sender_user_id` 可省略,省略表示接收该群/私聊中所有其他用户的消息;填写时只接收该人的消息。 `owner_pid` 可省略。填写时必须是已确认的当前交互 Codex 进程或桌面应用进程, 不能填写短命的 shell 命令进程、共享 app-server 守护进程或 MCP 自身来冒充窗口生命周期。 未绑定 owner 时 `exit_detection=manual`,不得承诺 `/exit` 一定停止监听。 调用返回 `starting` 后,用 `list_monitoring` 检查到 `listening` 才能告知用户开启成功。 首次监听按 VPS Desktop 的 `/im/ws` 鉴权握手读取 `ready.cursor` 建立最新事件基线,不扫描首次启用前的旧历史。 监听按“VPS 会话 + 可选发送者筛选”把已确认 cursor 和最近 1000 个已投递消息 ID 原子写入 `.vps-state/monitors.json`。MCP 重启、Codex 换新 thread 或网络恢复后,从该检查点调用 `/v1/im/sync` 补拉断线期间事件;消息投递确认后先保存消息 ID,再在整页处理完成后推进 cursor,避免崩溃窗口造成遗漏或重复执行。 监听恢复检查点独立于 `get_new_messages` 的持久 cursor,不保存消息正文、附件或凭据。`list_monitoring` 的 `resumed=true` 表示本次从已有检查点恢复;首次建立基线为 `false`。 正常每 5 秒拉取一次;有后续分页时继续读取。缺少 `has_more` 时,短页不能判定已经追平,继续读取到游标稳定的空页。忽略自己发送的消息、机器人消息、修改/撤回事件, 按消息 ID 去重。相同 thread + channel 重复开启返回原监听;修改过滤条件须先停止旧监听。 服务重启后不会在未经用户授权时自动重新开启监听;用户再次启用同一会话和发送者筛选后,会从持久检查点补拉停机期间的消息。 新消息通过本机 `codex queue --thread --message <消息>` 投递到原会话。 消息正文使用 JSON 数据封装,明确区分外部来源和本地用户指令,例如: ```json { "source": "VPS中 张三 的消息", "channel_name": "后端群", "channel_id": "实际会话 ID", "message_id": "实际消息 ID", "sender_user_id": "123", "body": "字段已经上线,可以继续联调。", "attachments": [] } ``` 名称取自 VPS 群成员昵称、员工姓名或登录名;无法解析时标为“VPS中 用户 123 的消息”,不编造姓名。 附件仅传递元数据,不自动下载,也不把未读取的文件内容当作已知内容。 AI 只在原会话已授权的任务范围内继续工作;只有获准对外回复时才使用原 channel_id 和 message_id 回传, 不能自动转发本地用户与 AI 的私聊。工具自身不会发送 VPS 消息。 ### 停止和退出 - 明确说“停止 VPS 监听”:AI 先 `list_monitoring(thread_id=当前 UUID)`,再逐一 `disable_monitoring(monitor_id=...)`。 - 绑定进程退出(包括 PID 被其他进程复用):独立检查线程约每 0.5 秒检查,自动停止;HTTP 请求仍在等待时也会取消后续投递。 - MCP 正常关闭:停止全部监听;进程被强制杀死时内存监听随进程消失。 - 当前轮 AI 完成、等待外部回复、人工继续聊天:保持监听。 - 桌面切换会话、关窗口但应用进程仍驻留,以及未绑定准确 owner 的终端退出:不能可靠检测。 **开启时必须主动告诉用户:“无法可靠检测关闭或切换,离开前请告诉 AI:停止 VPS 监听。”** - 停止后不自动恢复;再次使用需明确重新开启。 停止会丢弃尚未提交的同步结果,并终止尚未结束的投递子进程。 **已被 Codex 接收的排队输入无法撤回,停止监听不等于取消 Codex 已接收或正在执行的任务。** 投递失败/超时可能已被接收,因此停止该监听并标为 `error`,不会自动重试导致重复执行。 网络异常及 HTTP 408/429/500/502/503/504 会进入 `reconnecting`,按 2、4、8、16、30 秒(上限)退避重试,成功后恢复原状态和同一游标。重试只作用于读取,不能重试 Codex 投递。401/403 等其他 HTTP 错误及契约异常仍停止监听。 `list_monitoring` 返回最近成功同步时间、当前游标、累计重试次数、退避秒数,以及失败操作、HTTP 状态码和异常类型;不暴露服务端正文或凭据。停止操作和服务清理保留已经发生的错误原因,不覆盖成“手动停止”。 ### 验证范围与待联调 2026-10-27 已在本机 Codex CLI 0.164.0 核对 `queue --thread --message` 命令存在, 对当前 VPS 服务做只读响应结构检查(未修改持久 cursor、未发消息),确认 `payload.message`、`sender_user_id`、`user.userId`、群成员姓名等字段。 自动化测试覆盖历史基线、联系人/群/发送者过滤、去重、自身消息过滤、原 session 投递参数、 停止期间网络返回、PID 复用、服务退出、投递失败及来源标注。 尚需用用户选定的真实 VPS 会话和 Codex thread 联调:新消息实际唤起原会话、人工同时聊天、 指定客户端对共享 app-server 的接入方式、实际 `/exit` 与桌面关闭行为。 不能只凭 queue 命令存在就宣称所有桌面客户端都兼容;无法检测的退出使用明确告知的手动停止方式。 本轮没有创建真实监听,没有向联系人/群发送测试消息,也没有向现有 Codex 会话注入测试消息。 新增工具需要 MCP 客户端重新加载服务器/刷新工具列表后使用。 ### 2026-10-27 首次实机监听后的修正 首次显示 `listening` 后因 `VPSAPIError` 退出。旧实现未保存 HTTP 状态码,临时测试连接清理时又把错误覆盖成手动停止,因此不能追溯断言那次错误是限流、鉴权或服务端故障。 对照本机 `/Applications/VPS Desktop.app` 的 `out/main/index-CrNba87L.js`、`out/renderer/assets/index-B68zCxgn.js` 和随包 `vps-work`,确认桌面版使用 `/im/ws` 的 `auth` / `ready.cursor` 取得当前位置,HTTP 同步失败后保留定时轮询。 只读复现还发现:`/v1/im/sync` 未返回 `has_more`,不足 500 条的短页仍可能是 7 月历史记录,原先“短页即追平”的判断错误。故上述首次 `listening` 仅表示旧程序进入循环,不代表当时真正追上最新消息。 现已改用 WebSocket 当前游标建立基线,并修复短页判断、只读请求退避恢复和诊断状态保留。新增依赖 `websockets>=15,<16`;更新环境时先安装 `requirements.txt`。握手仅发送现有合法 Agent 凭据到配置服务的同源 `/im/ws`,不读取人工登录密钥、不发送聊天消息,取得游标后关闭临时 WebSocket。 本机已验证 Agent 凭据可以得到最新游标,随后连续 5 次增量拉取成功;历史接口异常在本轮复测中未复现。41 项自动化测试通过,包括最新基线、短页续读、临时错误重试、鉴权失败、退避期间停止与错误保留。真实新消息投递至当前 Codex 会话仍待对方发送消息验证。 ## 搜索与指定人员提醒(2026-10-27) `search_chats` 支持关键字和轻量模糊匹配,并忽略大小写、Unicode宽度和全部空白;例如“AI 销售内部技术群”可匹配“AI销售内部技术群”,少量同形错字也会按相似度排序。搜索范围仅包含会话名称、ID和成员,不会用最后消息正文误匹配群名,当前覆盖接口返回的最多500个会话。 `search_chats` 与 `list_recent_chats` 默认共用5分钟本地会话目录缓存,首次请求或缓存过期时才重新读取服务端;结果中的 `cache.source` 会标明 `network` 或 `cache`。需要立即看到新建群或成员变更时传 `refresh: true`。可用 `VPS_MCP_CHANNEL_CACHE_TTL_SECONDS` 调整有效期,或用 `VPS_MCP_CHANNEL_CACHE` 指定缓存文件。缓存按服务地址和 Agent 凭据摘要隔离,文件及目录权限分别为0600和0700,不保存凭据原文。 `send_message` 可传 `mention_user_ids: [用户ID]`,必须先从目标群成员确认ID,且正文必须含完全一致的 `@姓名`,否则会在发送前拒绝请求。工具再次读取成员核验,并按 VPS Desktop 客户端的 `metadata.mentions=[{type:"user",user_id,name}]` 协议发送;不会将普通@文本冒充真实提醒,不支持自动@所有人。发送后回读metadata确认,不能据此保证对方已读或收到推送。 更新源码后,新MCP进程加载新增参数;已有连接需重新连接。网络超时返回可识别错误,发送不自动重试。