跳到正文
GitHub·· 9 天前AI 评分66

MCPBox 开源:把 Android 手机变成 MCP 文件服务器

xtt-xt/MCPBox

AI 导读

MCPBox 是一个单 APK 的 Android 应用,把手机变成 MCP 文件服务器,AI 客户端通过 MCP 协议读写手机文件、执行命令,每次敏感操作都要经过手机悬浮窗审批。

正文

中文 | English

License: GPL v3 构建与签名

把 Android 手机变成一台 MCP 文件服务器:AI 客户端(RikkaHub / Claude Desktop / Cursor / Cline…)通过 MCP 协议读写手机文件、执行命令,而每一次敏感操作都要经过手机上的悬浮窗审批。

  • 单 APK,无外部依赖:HTTP/MCP 服务器、自带浏览器控制台、常驻终端全部在这一个进程里
  • 三档 shell 身份:应用沙箱 / Root / Shizuku(ADB shell)
  • 权限不是"一次性全允许",而是 8 个开关 × 三态 + 路径/命令级规则,审批弹窗浮在所有 App 之上
AI 客户端 ──HTTP(MCP)──▶ 手机上的 MCPBox ──▶ 文件系统 / shell
                              │
                              └─▶ 悬浮窗审批(允许一次 / 始终允许 / 拒绝 / 始终拒绝)

特性

72 个 MCP 工具 文件读写删、搜索、图片预览、回收站、设备信息、执行命令、自定义工具、令牌、记忆库导入导出、工具包、UI 自动化、内置浏览器…
工具包 工具分 8 个包,出厂默认只带 27 个(比全量少带约六成 token);包由你在 App 里配置,AI 无感
会话隔离 客户端地址填 /mcp/p/<名字> 就是独立会话,各有各的激活状态,可持久化 / 重置 / 删除
预设会话 权限页一键切换「全部允许 / 全部拒绝 / 全部询问 / 自定义」;切走时自动记住你的自定义设置,切回来原样恢复
逐次审批 顶层悬浮窗 + 通知栏兜底,支持「允许一次 / 始终允许 / 拒绝 / 始终拒绝」;审批方式可在设置里切换「悬浮窗 / 通知栏」
审批不限时 审批和命令超时都可以关掉(不限时):弹窗不会自动消失、命令一直跑到自己结束
权限矩阵 9 个权限键三态(允许 / 询问 / 拒绝)+ 路径规则、命令规则(前缀 / 完全 / 正则)
工具级权限四态 单个工具可单独设「跟随 / 允许 / 询问 / 拒绝」,其中「询问」无视全局矩阵、每次都弹窗
记忆库 给 AI 的长期记忆:实体 + 观察 + 关系(知识图谱),能在 App 里浏览和编辑
文件网关 POST /upload、GET /download,外加手机浏览器直接可用的上传网页(可多选);加 &extract=1 / &zip=1 还能整目录一次传 / 一次取(zip,一个请求 = 一次审批)
静态站点托管 浏览器打开 http://127.0.0.1:8720/web/?token=<令牌> 就能预览手机里的网页产物:路径相对主根目录(如 /web/xtt/web/flat-ui/index.html),有目录索引页、Range 与 ETag。要访问令牌,带一次就够(之后靠 /web 的会话 cookie,页面里的 CSS/JS 才能自动放行);只读
应用私有目录 可读可写 /data/data/<包名>,三档(禁止 / 只读 / 可读写),经 root 转发
UI 自动化 截屏 + 读界面结构(控件树/坐标)+ 点击 / 滑动 / 输入(含中文)/ 按键 / 启应用 / 等元素,需要 Shizuku 或 Root
内置浏览器 App 自己带一个 WebView:AI 能打开网页、读正文与可交互元素、点击 / 填表 / 滚页 / 等元素、执行 JS、网页截图、开多个页面、用各平台搜索、把网页存成文件、按页面管 cookie 与 User-Agent(这一页用手机版还是桌面版,只影响这一页)。不需要 Root / Shizuku;有页面时手机上会出现悬浮球,点开就能看页面、切页面、暂停 AI、自己接管
自动补齐权限 有 Root / Shizuku 时,进入 App 自动把缺的「文件访问 / 悬浮窗 / 忽略电池优化」静默开掉(走 appops 与电池白名单,不重启进程);首页也有「一键补齐」
内置终端 常驻 shell,cd/export 状态保留、命令历史、Ctrl-C 中断、清屏
自定义工具 用命令模板给自己造新 MCP 工具,支持参数占位与 JSON 导入导出
网页控制台 浏览器里试工具、看日志、处理审批;支持仅本机访问与密码保护
两种传输 Streamable HTTP(/mcp)与旧版 HTTP+SSE(/sse + /messages)
使用统计 每日请求热力图 + 累计运行时长 / 打开应用次数 / 服务器启动次数;只增不减,重置设置也不清,可随备份带走
备份与恢复 记忆库 / 设置 / 自定义工具 / 统计四部分,可打一个 zip 或逐部分导 json;恢复时各项自选「合并 / 覆盖」
初始引导 全新安装第一次打开走五步(欢迎 / 语言 / 权限 / 恢复备份 / 开始使用);升级不打扰,开发者模式可强制重走
多语言界面 中文 / English 内置,另可导出模板翻译成任意语言再导入

快速开始

  1. 安装 APK,打开 App → 首页点开服务(默认端口 8720)
  2. 给文件访问权限、悬浮窗权限、通知权限(首页「环境检查」会逐项提示)。 有 Root / Shizuku 的话,这几项(文件访问 / 悬浮窗 / 忽略电池优化)打开 App 时会自动补上, 也可以点「环境检查」下面的「一键补齐」;通知权限要自己在系统弹窗里点一下
  3. 在 AI 客户端里添加 MCP 服务器:
类型:Streamable HTTP(推荐)或 SSE
地址:http://<手机局域网IP>:8720/mcp
令牌:App 首页「连接地址」里复制的那个(含 token 的完整 URL)
  1. 需要 shell 权限时,去「终端」页申请 Shizuku 授权(可选:Root 会自动识别)

MCP 工具

文件(20):list_dir directory_tree file_info read_file read_image search_files file_hash write_file edit_file make_dir copy_path move_path delete_path list_trash restore_trash empty_trash server_info get_device_info storage_info notify_user

Shell / 扩展(8):run_shell shell_info create_custom_tool update_custom_tool delete_custom_tool list_custom_tools export_custom_tools import_custom_tools

UI 自动化(8):ui_screenshot ui_dump ui_tap ui_swipe ui_input ui_key ui_launch ui_wait (需要 Root 或 Shizuku:截屏、读控件树、注入点击都是系统权限,应用自身 UID 做不到。 ui_input 输中文时自动走「写剪贴板 → 模拟粘贴」)

令牌(1):get_token(调用它自己不需要令牌,用于解开「AI 要先拿到 token 才能传文件」的死循环)

工具包(5):list_packs activate_pack deactivate_pack reset_packs(免审批,只影响可见性)、manage_pack(建/改/删自定义包,走「自定义工具」权限)

内置浏览器(18):browser_open browser_navigate browser_history browser_pages browser_switch browser_close browser_content browser_click browser_input browser_scroll browser_wait browser_eval browser_screenshot browser_search browser_save browser_storage browser_engines browser_download (页面在 App 的 WebView 里跑,不需要 Root / Shizuku;URL 默认禁内网与 localhost, 防止网页脚本去调本机的 MCP 端口。有页面时手机上会出现悬浮球:点开可以看页面、切页面、 暂停 AI、自己接管操作。登录态与系统浏览器隔离,只属于 MCPBox。 cookie 与 User-Agent 可以按页面管:browser_open / browser_navigate 能直接带 ua(手机版 / 桌面版)与 cookies,browser_storage 能只清某个站点的 cookie、只改某一页的身份)

记忆库(10):create_entities create_relations add_observations delete_entities delete_relations delete_observations read_graph search_nodes open_nodes memory_stats

权限模型

权限键 默认 说明
fs.read 允许 读文件、列目录、看图
fs.write 询问 写入、修改、新建、复制、移动
fs.delete 询问 删除(默认还会先进回收站)
shell.exec 询问 执行命令(用户自己在终端敲的不算)
tools.manage 询问 新建/修改/删除自定义工具
ui.control 询问 控制屏幕:截屏、读界面结构、点击、滑动、输入(需要 Root / Shizuku)
browser.control 询问 浏览器:打开网页、点击、填表、执行脚本、读写 cookie(默认每次都问)
system.info 允许 设备信息、存储信息、获取令牌
memory 允许 记忆库的读写(关掉总开关后记忆工具直接从 tools/list 消失)

**规则(Rule)**可以覆盖开关,粒度更细:

  • 路径规则:/data/data/me.rerere.rikkahub/ → 允许写(最长前缀优先)
  • 命令规则:pm list packages 前缀 → 允许;rm -rf 正则 → 拒绝(按顺序首条命中)

在审批弹窗上点「始终允许 / 始终拒绝」会自动生成对应规则,之后同类操作不再打扰。

预设会话(权限页最上面):一键把上面 8 个开关统一成同一个值 ——

预设 效果
全部允许 所有开关设为「允许」,AI 不再被打断
全部拒绝 所有开关设为「拒绝」,AI 只能读服务器状态
全部询问 所有开关设为「询问」,每个敏感动作都弹窗(最安全)
自定义 解锁,逐个调 8 个开关

选前三个时会锁住下面的开关(整行不可点)—— 避免出现"写着全部允许、其实写删是询问"这种看不懂的状态。 切到固定预设之前,App 会先把你当前那份自定义设置存下来;之后切回「自定义」原样恢复,调过的不会被抹掉。

单个工具还能在「设置 → 工具管理」里单独设权限,四态含义:

取值 含义
跟随(默认) 按上面的全局权限矩阵走
允许 这个工具的所有操作直接放行,不弹审批
询问 无视全局矩阵,这个工具每次调用都弹窗问你一次
拒绝 无论全局怎么设,一律拒绝

在「询问」触发的弹窗里选「始终允许 / 始终拒绝」,记住的是这个工具本身,不会改动全局权限开关。

超时设置

两处超时都能关掉(不限时):

设置项 默认 不限时后
审批超时 300 秒 弹窗不会自动消失,AI 一直等你答复(值存 0)
命令默认超时 300 秒 命令一直跑到自己结束,不会中途被掐断

run_shell 和自定义工具的 timeoutMs 参数同样支持 0 = 不限制。

记忆库

给 AI 的长期记忆,结构是经典的知识图谱三元组:

概念 说明
实体 一个节点:项目、工具、事件、人物、用户偏好… 名字唯一
观察 挂在实体身上的一条条事实短句(增删时自动去重)
关系 两个实体之间的有向边:from ──PART_OF──▸ to

实体类型、分区(folder)、关系谓词都是自由文本,不强制枚举 —— AI 自己会自然长出 「项目事实 / 用户偏好 / 事件」这类结构,关系谓词一般用大写下划线(PART_OF、HAPPENS_AT、 INVOLVES、CORRECTS、UPDATES…)。

  • 界面:设置 → AI 工具 → 记忆库。支持搜索、按分区过滤,点进实体可以改名 / 改类型 / 改分区、增删观察、增删关系;点关系能跳到对方实体。
  • 存储:filesDir/memory/graph.json,原子写入(先写 .tmp 再改名),断电最多丢最后一次写入。
  • 搜索 search_nodes 会连带返回命中实体的直接邻居,方便顺着线索往下读。
  • 重名实体只跳过不覆盖;重命名会自动同步关系两端;删实体会连带删掉它的所有关系。

统计

设置 → 统计。全是只增不减的长期记录:

指标 口径
请求热力图 POST /mcp 的每条 JSON-RPC 消息算一次(含 initialize / ping / 通知),按设备本地自然日分组
累计运行时长 服务器在跑就累加;页面上每秒刷新
打开应用次数 每次打开 App 记一次(一个进程一次,转屏 / 切回任务不算)
服务器启动次数 每次服务启动(含重启)记一次
有请求的天数 一年里有多少天有请求
  • 存储:filesDir/stats/stats.json,原子写入;请求按 15 秒批量落盘,进程被杀最多丢十几秒
  • 永远保留:不在 SharedPreferences 里,「重置全部设置」也不会清掉(没有清空入口)
  • 备份:备份里的第四部分「统计」;恢复时合并 = 按天取较大值(恢复两次不会翻倍)

工具包

工具定义要在每一轮请求里重复带给模型。全部 72 个工具的定义合计约 3.6 万字符(按 4 字符 ≈ 1 token 粗算,约 9000 token), 聊 20 轮就是 13 万——而且大部分轮次根本用不到那么多工具。

所以把工具分包,tools/list 只返回基础包 + 已激活包里的工具:

包 工具数 出厂
core 基础 4 常驻
file.read 文件读取 8 开
memory 记忆库 10 开
file.write 文件写入 9 关
shell 命令与自定义工具 8 关
ui UI 自动化 8 关
browser 浏览器 18 关
my.tools 我的工具 动态 关

出厂默认 27 个工具约是全量的四成,比全带上省约六成。

包是你自己的长期设置:在「权限 → 工具包」里勾好,AI 看到什么就用什么, 完全不知道有包这回事 —— 零摩擦,也不浪费对话轮次。也可以自己建包 (App 里「+ 新建工具包」,或让 AI 用 manage_pack):把常用工具组合起来,起个名字、写清用途。

⚠️ 改完要重连才生效 主流 MCP 客户端只在连接时拉一次工具列表,之后不会再拉(服务端也没法通知它刷新)。 所以改完工具包后,需要重新连接 MCP 服务(或重启 App),AI 才会看到变化。

可选:让 AI 自己开关包(默认关)

「权限 → 工具包」里有这个开关。打开后 AI 能看到并调用 5 个包管理工具 (list_packs / activate_pack / deactivate_pack / reset_packs / manage_pack), initialize 的说明里也会列出还没激活的包。

为什么不默认开:实测主流客户端只在连接时拉一次工具列表,而客户端还会按自己缓存的 工具表拦截调用 —— 所以 AI 激活了一个包,这一轮、下一轮都调不到里面的工具, 白白浪费好几轮对话。默认关掉,既省下那 5 个工具的 token,也不给 AI 挖坑。

会话隔离:/mcp/p/<名字>

MCP 协议层面无法感知「AI 开了新对话」——客户端只在启动时 initialize 一次, 之后所有对话共用同一条连接。所以用请求路径来区分:

/mcp            → 默认会话
/mcp/p/编码     → 编码场景(可以自己激活 file.write + shell)
/mcp/p/写作     → 写作场景(只要 memory)

每份状态存在 filesDir/profiles/<名字>.json,重启 App 也保留; 在权限页可以选会话、重置、删除(每行右边的垃圾桶,default 是兜底会话不给删)。

TTL 兜底(可关,默认 30 分钟):超过这段时间没请求就回到默认包集, 这样「隔一阵开新对话」拿到的也是干净状态。设置 → AI 工具 →「会话状态自动重置」。

文件进出通道

不依赖 root,也不需要数据线:

# 推文件进手机(原始 body 即文件内容)
curl -X POST --data-binary @app.apk \
  "http://<手机IP>:8720/upload?path=/storage/emulated/0/xtt/app/mcp/app.apk&token=<token>"

# 取文件出手机
curl -o back.apk \
  "http://<手机IP>:8720/download?path=/storage/emulated/0/xtt/app/mcp/app.apk&token=<token>"

上传走「写文件」权限、下载走「读文件」权限,照旧弹审批。手机浏览器打开 http://127.0.0.1:8720/upload 还有一个上传网页(选文件 + 填路径即可)。

静态站点托管

把网页产物直接放进手机,浏览器打开就能看 —— 不用数据线、不用起服务器:

http://127.0.0.1:8720/web/?token=<令牌>                        打开站点列表
http://127.0.0.1:8720/web/xtt/web/flat-ui/index.html?token=<令牌>
  • 要访问令牌,但只用带一次:第一个请求的地址里放 ?token=…,服务器就给浏览器种一个 Path=/web 的 HttpOnly 会话 cookie(12 小时有效),页面里的 CSS / JS / 字体这些子资源 由浏览器自动带 cookie —— 这正是不能只靠 ?token= 的原因(浏览器自己去要子资源时, 不会把地址栏里的 query 带过去)。不想手输令牌:从网页控制台的「静态站点」卡片点进去, 那个链接自带令牌。
  • 令牌三种带法都认:?token= / Authorization: Bearer / X-MCP-Token。
  • 路径相对主根目录:/web/a/b.html 就是 <主根目录>/a/b.html;目录会给一张索引页, 里面的 index.html 会被标成「这个目录的入口页」。
  • 只读:没有 PUT / DELETE,写文件仍然只能走 /upload(那条路要过审批)。
  • <主根目录>/.MCPBox(App 自己的目录,含回收站)不给看;索引页也不列隐藏项。
  • 单文件上限 32MB(预览是整个读进内存再发的);更大的文件用 /download 取出去看。
  • 权限口径:只有 fs.read 的**「拒绝」**能拦住它(「询问」不弹窗 —— 一个页面几十个子资源, 逐个审批没法用)。
  • 把令牌功能整个关掉(tokenEnabled=false)时,这条路由跟着放开,口径与 /download 一致。

应用私有目录

应用自身读不到别家私有目录,所以开启后会经 root / Shizuku 转发:

  • 三档:禁止(默认)/ 只读 / 可读写
  • 覆盖范围:/data/data、/data/user/0、/data/user_de/0、/data/local/tmp、/data/app、/data/misc、/data/system、/data/adb
  • 想只放开某一个应用,用路径规则单独设置;只读模式下写/删会被直接拒绝
  • Android 15+ 的 mount namespace 隔离会让 root 也"看不全"应用数据,本项目会自动探测 su -mm / su -M 进入 global namespace

网页控制台安全

开关 作用
仅本机访问 只响应 127.0.0.1 / ::1,局域网设备一律 403
密码保护 浏览器需登录(cookie 会话 12 小时);程序用 token 调用不受影响
访问密码 单独设置,留空则用访问令牌(token)当密码

架构

mcpcore/   纯 Kotlin/JVM 零 Android 依赖:HTTP 服务器、MCP 协议、工具实现、
           权限矩阵、文件桥、文件网关(可单独跑、可测)
harness/   端到端测试:真起一个 HTTP 服务器,用真请求打所有工具与安全策略
app/       Android 层:Compose UI、前台服务、悬浮窗审批、Shizuku 接入、终端

构建

# 端到端测试(434 项断言)
./gradlew :harness:e2e

# 构建
./gradlew :app:assembleDebug        # 调试包
./gradlew :app:assembleRelease      # 正式包(无密钥时产出未签名 APK)

要求:JDK 17、Android SDK(compileSdk 35)。Gradle 用仓库自带的 wrapper(8.13)。

签名与发布

签名信息不入库(keystore/ 已在 .gitignore 里)。构建时按环境变量读取:

环境变量 说明
KEYSTORE_PATH 密钥路径,默认 keystore/release.keystore
KEYSTORE_PASSWORD keystore 密码
KEY_ALIAS 别名
KEY_PASSWORD 别名密码

没有密钥也能构建 —— 只是 release 包不会被签名。

GitHub Actions

仓库自带 .github/workflows/build.yml:推送到 main、发 PR 或打 v* 标签时自动跑测试 + 构建,产物在 Actions 的 Artifacts 里;打标签时还会自动创建 Release 并附上 APK。

想让 CI 产出已签名的 APK,在仓库 Settings → Secrets and variables → Actions 里加四个 Secret:

Secret 怎么填
KEYSTORE_BASE64 base64 -w0 keystore/release.keystore(本机执行后整段粘贴)
KEYSTORE_PASSWORD keystore 密码
KEY_ALIAS 别名
KEY_PASSWORD 别名密码

发布一个版本:

git tag v1.1.0 && git push origin v1.1.0

目录结构

app/                          Android 应用(Compose UI + 服务 + 悬浮窗)
  src/main/java/com/xtt/mcpbox/
    ui/                       界面:首页/终端/权限/日志/设置/工具管理/记忆库
    browser/BrowserController.kt  内置浏览器:WebView + 悬浮球 + 可拖动悬浮窗
    server/                   前台服务、审批广播、开机自启
    AndroidHost.kt            设备信息、通知等系统能力
    ShizukuShell.kt           Shizuku 进程启动器
mcpcore/src/main/kotlin/com/xtt/mcpbox/core/
  HttpServer.kt               迷你 HTTP/1.1(keep-alive、chunked、SSE)
  McpServer.kt                路由、鉴权、MCP 协议、网页控制台安全
  McpTools.kt / ToolsRead.kt / ToolsWrite.kt / ToolsShell.kt
  Approval.kt                 权限矩阵与审批中心
  PathSandbox.kt / TrashManager.kt / FileBridge.kt / FileGateway.kt
  Shell.kt                    shell 后端(应用 / root / Shizuku)
  WebConsole.kt / Config.kt / CustomTools.kt / EventLog.kt
  Memory.kt                   记忆库:实体 + 观察 + 关系,原子写入 graph.json
  ToolPack.kt                 工具包:内置包定义 + 自定义包存储
  ToolsUi.kt                  UI 自动化:截屏 / 控件树 / 点击 / 滑动 / 输入
  ToolsBrowser.kt             浏览器工具包:18 个 browser_* 工具
  Browser.kt                  浏览器内核:URL 规则 / 内网拦截 / 搜索引擎 / 注入页面的 JS
  ProfileStore.kt             会话(URL profile):激活状态 + TTL,原子写盘
  ToolsPacks.kt / ToolsMemory.kt / ToolsToken.kt / ToolMeta.kt / ToolPolicy.kt / LocalNet.kt
harness/                      端到端测试

许可

Copyright (C) 2026 xtt

本项目以 GNU General Public License v3.0(GPL-3.0)发布,完整条款见 LICENSE。

本项目依赖均为 Apache-2.0 / MIT 等宽松许可(AndroidX、Jetpack Compose、Kotlin、kotlinx-coroutines、Shizuku),与 GPL-3.0 兼容。

来源:GitHub · github.com