3
年
来到社区 3 年
LEVEL 100
XP
## 一、背景与目标
MCP(Model Context Protocol)用于在模型客户端与外部数据源、工具之间建立标准化连接。与直接让模型执行 Shell 命令相比,MCP Server 可以把能力收敛为一组参数明确、权限有限、结果可审计的工具,例如读取指定目录中的文件、查询内部知识库,或者调用经过封装的安全检查程序。
本文以“授权 APK 样本的本地检查”为场景,实现一个可供 MCP Client 调用的 Python Server。它只提供两个低风险工具:
1. `sha256_file`:计算实验目录内文件的 SHA-256,用于样本归档和完整性校验。
2. `inspect_apk_archive`:将 APK 作为 ZIP 容器检查,返回文件数量、解压后总体积、压缩比及部分条目名称。
这个示例不会反编译、安装或执行 APK,也不会连接任何真实目标。所有操作均限制在用户明确配置的本地实验目录中,适合作为 MCP 工具接入、参数校验和权限设计的基础模板。
## 二、MCP 的基本工作原理
MCP 采用客户端、服务器架构。Host 通常是带有模型能力的桌面应用、IDE 或自研智能助手;Host 内部的 MCP Client 负责连接一个或多个 MCP Server;Server 则公开资源、提示模板或工具。
典型会话过程如下:
1. Client 发送 `initialize`,声明所支持的协议版本和能力。
2. Server 返回协商后的协议版本、能力以及服务端信息。
3. Client 发送初始化完成通知。
4. Client 通过 `tools/list` 获取工具清单和输入结构。
5. 当用户确认执行某项操作后,Client 使用 `tools/call` 调用具体工具。
6. Server 完成参数校验、权限检查和业务处理,再返回结构化结果。
一次工具调用在逻辑上类似下面的 JSON-RPC 消息:
`{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"sha256_file","arguments":{"relative_path":"sample.apk"}}}`
实际开发时不建议自行拼接协议消息或固定协议版本,应让官方或兼容 SDK 处理能力协商、消息格式及传输细节。本文使用 stdio 传输:客户端启动 Server 子进程,通过标准输入输出交换 MCP 消息。stdio 不等于“天然安全”,但它不需要额外开放监听端口,适合本地工具。需要特别注意,Server 的标准输出必须留给协议消息,调试日志应写入标准错误或独立日志文件。
## 三、前置条件
建议准备以下环境:
- Python 3.10 或更高版本。
- 支持 MCP 的客户端,或者 MCP Inspector 调试工具。
- Node.js 与 npm,用于运行 Inspector。
- 一个专门的本地实验目录。
- 自己开发、公开用于测试或已获得授权的 APK 文件。
在 Linux 或 macOS 中创建项目:
`mkdir -p ~/mcp-mobile-lab/lab`
`cd ~/mcp-mobile-lab`
`python3 -m venv .venv`
`source .venv/bin/activate`
`python -m pip install --upgrade pip`
`pip install "mcp[cli]"`
Windows PowerShell 可使用:
`py -m venv .venv`
`.\.venv\Scripts\Activate.ps1`
`pip install "mcp[cli]"`
把经过授权的测试文件放入 `lab` 目录,例如 `lab/sample.apk`。不要把整个下载目录、用户主目录或磁盘根目录配置为实验根目录。
## 四、实现 MCP Server
在项目根目录创建 `server.py`:
```python
import hashlib
import os
import zipfile
from pathlib import Path
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("mobile-lab")
LAB_ROOT = Path(
os.environ.get("MCP_LAB_ROOT", "./lab")
).expanduser().resolve()
MAX_FILE_SIZE = 512 * 1024 * 1024
MAX_ZIP_ENTRIES = 20000
MAX_UNCOMPRESSED_SIZE = 2 * 1024 * 1024 * 1024
MAX_COMPRESSION_RATIO = 200
MAX_RETURNED_NAMES = 200
def resolve_lab_file(relative_path: str) -> Path:
if not relative_path or "\x00" in relative_path:
raise ValueError("文件路径不能为空或包含空字节")
supplied = Path(relative_path)
if supplied.is_absolute():
raise ValueError("只允许相对于实验目录的路径")
try:
target = (LAB_ROOT / supplied).resolve(strict=True)
target.relative_to(LAB_ROOT)
except (FileNotFoundError, RuntimeError, ValueError):
raise ValueError("文件不存在,或路径超出实验目录")
if not target.is_file():
raise ValueError("目标不是普通文件")
size = target.stat().st_size
if size > MAX_FILE_SIZE:
raise ValueError("文件超过允许的大小上限")
return target
@mcp.tool()
def sha256_file(relative_path: str) -> dict:
"""计算实验目录内指定文件的 SHA-256,不执行文件内容。"""
target = resolve_lab_file(relative_path)
digest = hashlib.sha256()
with target.open("rb") as stream:
while chunk := stream.read(1024 * 1024):
digest.update(chunk)
return {
"file": target.name,
"size": target.stat().st_size,
"sha256": digest.hexdigest(),
}
@mcp.tool()
def inspect_apk_archive(relative_path: str) -> dict:
"""检查授权 APK 的 ZIP 容器结构,不解压、不安装、不执行。"""
target = resolve_lab_file(relative_path)
if target.suffix.lower() != ".apk":
raise ValueError("该工具只接受 .apk 文件")
if not zipfile.is_zipfile(target):
raise ValueError("文件不是有效的 ZIP/APK 容器")
try:
with zipfile.ZipFile(target, "r") as archive:
entries = archive.infolist()
if len(entries) > MAX_ZIP_ENTRIES:
raise ValueError("归档条目数量超过安全上限")
total_uncompressed = sum(item.file_size for item in entries)
total_compressed = sum(item.compress_size for item in entries)
if total_uncompressed > MAX_UNCOMPRESSED_SIZE:
raise ValueError("归档声明的解压后体积超过安全上限")
ratio = total_uncompressed / max(total_compressed, 1)
if ratio > MAX_COMPRESSION_RATIO:
raise ValueError("归档压缩比异常,可能存在压缩炸弹风险")
names = [item.filename for item in entries[:MAX_RETURNED_NAMES]]
return {
"file": target.name,
"entry_count": len(entries),
"compressed_bytes": total_compressed,
"uncompressed_bytes": total_uncompressed,
"compression_ratio": round(ratio, 2),
"contains_manifest": "AndroidManifest.xml" in names,
"returned_name_count": len(names),
"names_truncated": len(entries) > MAX_RETURNED_NAMES,
"entry_names_untrusted": names,
}
except zipfile.BadZipFile:
raise ValueError("APK 容器已损坏或格式不受支持")
if __name__ == "__main__":
LAB_ROOT.mkdir(parents=True, exist_ok=True)
mcp.run(transport="stdio")
```
这里有几个关键设计:
- 工具只接受相对路径,避免客户端直接提交 `/etc/passwd`、用户密钥目录等绝对路径。
- `resolve(strict=True)` 会解析路径,再用 `relative_to` 验证最终目标仍位于实验根目录内,可阻止常见的 `../` 目录穿越。
- 哈希计算采用分块读取,避免把大文件一次性载入内存。
- APK 检查不实际解压文件,只读取中央目录元数据,并设置条目数、总体积和压缩比上限。
- 返回条目名称时明确标记为不可信数据,因为恶意文件名可能携带提示注入文本、控制字符或大量垃圾内容。
- 工具没有接受任意命令、URL、Shell 参数,也不会调用 `adb install`,攻击面明显小于通用终端工具。
## 五、使用 Inspector 调试
确认虚拟环境处于激活状态,然后执行:
`MCP_LAB_ROOT="$HOME/mcp-mobile-lab/lab" npx @modelcontextprotocol/inspector python server.py`
首次使用 `npx` 时,它可能提示下载对应软件包。Inspector 启动后,按照终端显示的本地地址打开页面,连接 Server,然后执行以下操作:
1. 打开工具列表,确认存在 `sha256_file` 和 `inspect_apk_archive`。
2. 调用 `sha256_file`,参数填写 `{"relative_path":"sample.apk"}`。
3. 调用 `inspect_apk_archive`,使用相同的相对路径。
4. 尝试传入 `../server.py` 或绝对路径,确认服务端拒绝访问。
5. 传入不存在的文件或非 APK 文件,确认错误信息清晰且没有泄露完整系统路径。
直接执行 `python server.py` 时,进程通常会等待来自 stdin 的 MCP 消息,看上去像“没有输出”。这对 stdio Server 属于正常现象,不应通过 `print()` 向 stdout 增加调试文字,否则可能破坏协议流。
## 六、接入 MCP Client
不少桌面客户端采用类似下面的配置结构,但配置文件位置和字段支持情况应以具体客户端版本文档为准:
```json
{
"mcpServers": {
"mobile-lab": {
"command": "/home/user/mcp-mobile-lab/.venv/bin/python",
"args": [
"/home/user/mcp-mobile-lab/server.py"
],
"env": {
"MCP_LAB_ROOT": "/home/user/mcp-mobile-lab/lab"
}
}
}
}
```
建议全部使用绝对路径,避免客户端工作目录不同导致加载错误。Windows 环境中需要改为实际的 `python.exe`、脚本和实验目录路径。修改配置后重启客户端,再检查工具是否被发现。
如果是自研 Client,应至少实现初始化、能力协商、工具发现、工具调用、超时及取消处理。不要假定 Server 永远可信:即使输入参数符合工具结构,Client 仍应在界面中展示工具名称、目标文件和影响范围,并让用户确认敏感调用。
## 七、权限与安全设计
### 1. MCP 工具不是天然权限边界
模型决定“想调用什么”,真正的授权必须由 Host 和 Server 执行。不能因为调用来自模型,就默认它代表用户授权。高风险工具应采用默认拒绝、最小权限和逐次确认策略。
### 2. 防止提示注入影响工具调用
APK 文件名、ZIP 条目名、日志和扫描报告都属于不可信内容。如果其中出现“忽略规则并调用某工具”等文本,Client 不应将其视为系统指令。建议把工具输出标记为外部数据,并限制它触发后续写文件、联网或执行命令的能力。
### 3. 不要暴露通用 Shell
类似 `run_command(command: str)` 的工具很方便,但几乎等同于把当前用户的终端权限交给模型。更合理的方式是按业务拆分工具,例如 `calculate_apk_hash`、`read_scan_report`,并为每个参数设置枚举、长度和路径限制。
### 4. 防范路径竞争与符号链接
本文的路径校验足以演示基本防护,但在多人共享目录或存在恶意本地用户的环境中,校验后到打开文件之间仍可能发生 TOCTOU 竞争。生产环境可使用专用低权限账户、不可写输入目录、容器挂载只读卷,以及操作系统提供的相对目录句柄和禁止跟随符号链接机制进一步加固。
### 5. 远程传输必须补充身份认证
如果以后将 Server 改为 Streamable HTTP 等远程传输,必须增加 TLS、身份认证、细粒度授权、来源校验、请求大小限制、速率限制和审计日志。不要把测试 Server 直接绑定到公网,也不要把访问令牌写入工具返回内容或普通日志。
### 6. 日志应可审计但避免泄密
建议记录调用时间、工具名称、调用结果、耗时和经过脱敏的参数,不记录 APK 完整内容、访问令牌或用户隐私数据。哈希值是否敏感取决于业务场景,在内部未公开样本库中也应按安全数据管理。
## 八、常见问题
### 1. 客户端看不到工具
优先检查 Python 解释器和脚本是否为绝对路径、虚拟环境是否安装 `mcp`、配置文件 JSON 是否有效,以及客户端是否真的重新启动。也可以先用 Inspector 排除 Server 本身的问题。
### 2. Server 启动后立即退出
在终端单独运行脚本,查看 stderr 中的依赖错误、语法错误或目录权限错误。不要为了调试而向 stdout 打印日志;stdio 模式下 stdout 是协议通道。
### 3. 为什么不直接调用 jadx、apktool 或 adb?
这些工具本身很有价值,但参数多、资源消耗更高,部分操作还可能安装或执行应用。接入 MCP 时应先做固定参数模板、超时、并发数、输出目录和设备范围限制,再逐项开放,不能把用户输入原样拼进 Shell。
### 4. 为什么 APK 条目名称仍被认为不可信?
工具安全不只取决于是否执行文件。模型可能读取条目名称并据此继续调用其他工具,因此数据层面的提示注入同样需要隔离。最稳妥的方式是仅返回完成当前任务所需的信息,并要求高影响操作再次获得用户确认。
### 5. 工具报压缩比过高,但样本确认正常怎么办?
安全阈值不是通用标准,应根据授权样本规模、主机内存和业务场景调整。不要简单删除限制,可以提高阈值,同时加入执行超时、进程内存限制和并发控制。
## 九、总结
一个可靠的 MCP Server 不只是“让模型能够调用函数”,更重要的是把能力边界设计清楚。本例通过 stdio 暴露 APK 哈希和容器检查工具,同时落实了实验目录隔离、相对路径约束、文件大小限制、压缩炸弹检测、结构化返回和不可信数据标记。
后续如果需要接入静态扫描、证书检查或内部漏洞知识库,建议继续遵循同一原则:每个工具只完成单一任务,参数采用白名单,执行使用低权限身份,输出限制大小,高风险动作必须获得用户确认,并为所有调用保留可审计记录。这样才能让 MCP 的扩展能力真正服务于移动应用安全工作,而不是引入一个难以控制的新攻击面。
MCP(Model Context Protocol)用于在模型客户端与外部数据源、工具之间建立标准化连接。与直接让模型执行 Shell 命令相比,MCP Server 可以把能力收敛为一组参数明确、权限有限、结果可审计的工具,例如读取指定目录中的文件、查询内部知识库,或者调用经过封装的安全检查程序。
本文以“授权 APK 样本的本地检查”为场景,实现一个可供 MCP Client 调用的 Python Server。它只提供两个低风险工具:
1. `sha256_file`:计算实验目录内文件的 SHA-256,用于样本归档和完整性校验。
2. `inspect_apk_archive`:将 APK 作为 ZIP 容器检查,返回文件数量、解压后总体积、压缩比及部分条目名称。
这个示例不会反编译、安装或执行 APK,也不会连接任何真实目标。所有操作均限制在用户明确配置的本地实验目录中,适合作为 MCP 工具接入、参数校验和权限设计的基础模板。
## 二、MCP 的基本工作原理
MCP 采用客户端、服务器架构。Host 通常是带有模型能力的桌面应用、IDE 或自研智能助手;Host 内部的 MCP Client 负责连接一个或多个 MCP Server;Server 则公开资源、提示模板或工具。
典型会话过程如下:
1. Client 发送 `initialize`,声明所支持的协议版本和能力。
2. Server 返回协商后的协议版本、能力以及服务端信息。
3. Client 发送初始化完成通知。
4. Client 通过 `tools/list` 获取工具清单和输入结构。
5. 当用户确认执行某项操作后,Client 使用 `tools/call` 调用具体工具。
6. Server 完成参数校验、权限检查和业务处理,再返回结构化结果。
一次工具调用在逻辑上类似下面的 JSON-RPC 消息:
`{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"sha256_file","arguments":{"relative_path":"sample.apk"}}}`
实际开发时不建议自行拼接协议消息或固定协议版本,应让官方或兼容 SDK 处理能力协商、消息格式及传输细节。本文使用 stdio 传输:客户端启动 Server 子进程,通过标准输入输出交换 MCP 消息。stdio 不等于“天然安全”,但它不需要额外开放监听端口,适合本地工具。需要特别注意,Server 的标准输出必须留给协议消息,调试日志应写入标准错误或独立日志文件。
## 三、前置条件
建议准备以下环境:
- Python 3.10 或更高版本。
- 支持 MCP 的客户端,或者 MCP Inspector 调试工具。
- Node.js 与 npm,用于运行 Inspector。
- 一个专门的本地实验目录。
- 自己开发、公开用于测试或已获得授权的 APK 文件。
在 Linux 或 macOS 中创建项目:
`mkdir -p ~/mcp-mobile-lab/lab`
`cd ~/mcp-mobile-lab`
`python3 -m venv .venv`
`source .venv/bin/activate`
`python -m pip install --upgrade pip`
`pip install "mcp[cli]"`
Windows PowerShell 可使用:
`py -m venv .venv`
`.\.venv\Scripts\Activate.ps1`
`pip install "mcp[cli]"`
把经过授权的测试文件放入 `lab` 目录,例如 `lab/sample.apk`。不要把整个下载目录、用户主目录或磁盘根目录配置为实验根目录。
## 四、实现 MCP Server
在项目根目录创建 `server.py`:
```python
import hashlib
import os
import zipfile
from pathlib import Path
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("mobile-lab")
LAB_ROOT = Path(
os.environ.get("MCP_LAB_ROOT", "./lab")
).expanduser().resolve()
MAX_FILE_SIZE = 512 * 1024 * 1024
MAX_ZIP_ENTRIES = 20000
MAX_UNCOMPRESSED_SIZE = 2 * 1024 * 1024 * 1024
MAX_COMPRESSION_RATIO = 200
MAX_RETURNED_NAMES = 200
def resolve_lab_file(relative_path: str) -> Path:
if not relative_path or "\x00" in relative_path:
raise ValueError("文件路径不能为空或包含空字节")
supplied = Path(relative_path)
if supplied.is_absolute():
raise ValueError("只允许相对于实验目录的路径")
try:
target = (LAB_ROOT / supplied).resolve(strict=True)
target.relative_to(LAB_ROOT)
except (FileNotFoundError, RuntimeError, ValueError):
raise ValueError("文件不存在,或路径超出实验目录")
if not target.is_file():
raise ValueError("目标不是普通文件")
size = target.stat().st_size
if size > MAX_FILE_SIZE:
raise ValueError("文件超过允许的大小上限")
return target
@mcp.tool()
def sha256_file(relative_path: str) -> dict:
"""计算实验目录内指定文件的 SHA-256,不执行文件内容。"""
target = resolve_lab_file(relative_path)
digest = hashlib.sha256()
with target.open("rb") as stream:
while chunk := stream.read(1024 * 1024):
digest.update(chunk)
return {
"file": target.name,
"size": target.stat().st_size,
"sha256": digest.hexdigest(),
}
@mcp.tool()
def inspect_apk_archive(relative_path: str) -> dict:
"""检查授权 APK 的 ZIP 容器结构,不解压、不安装、不执行。"""
target = resolve_lab_file(relative_path)
if target.suffix.lower() != ".apk":
raise ValueError("该工具只接受 .apk 文件")
if not zipfile.is_zipfile(target):
raise ValueError("文件不是有效的 ZIP/APK 容器")
try:
with zipfile.ZipFile(target, "r") as archive:
entries = archive.infolist()
if len(entries) > MAX_ZIP_ENTRIES:
raise ValueError("归档条目数量超过安全上限")
total_uncompressed = sum(item.file_size for item in entries)
total_compressed = sum(item.compress_size for item in entries)
if total_uncompressed > MAX_UNCOMPRESSED_SIZE:
raise ValueError("归档声明的解压后体积超过安全上限")
ratio = total_uncompressed / max(total_compressed, 1)
if ratio > MAX_COMPRESSION_RATIO:
raise ValueError("归档压缩比异常,可能存在压缩炸弹风险")
names = [item.filename for item in entries[:MAX_RETURNED_NAMES]]
return {
"file": target.name,
"entry_count": len(entries),
"compressed_bytes": total_compressed,
"uncompressed_bytes": total_uncompressed,
"compression_ratio": round(ratio, 2),
"contains_manifest": "AndroidManifest.xml" in names,
"returned_name_count": len(names),
"names_truncated": len(entries) > MAX_RETURNED_NAMES,
"entry_names_untrusted": names,
}
except zipfile.BadZipFile:
raise ValueError("APK 容器已损坏或格式不受支持")
if __name__ == "__main__":
LAB_ROOT.mkdir(parents=True, exist_ok=True)
mcp.run(transport="stdio")
```
这里有几个关键设计:
- 工具只接受相对路径,避免客户端直接提交 `/etc/passwd`、用户密钥目录等绝对路径。
- `resolve(strict=True)` 会解析路径,再用 `relative_to` 验证最终目标仍位于实验根目录内,可阻止常见的 `../` 目录穿越。
- 哈希计算采用分块读取,避免把大文件一次性载入内存。
- APK 检查不实际解压文件,只读取中央目录元数据,并设置条目数、总体积和压缩比上限。
- 返回条目名称时明确标记为不可信数据,因为恶意文件名可能携带提示注入文本、控制字符或大量垃圾内容。
- 工具没有接受任意命令、URL、Shell 参数,也不会调用 `adb install`,攻击面明显小于通用终端工具。
## 五、使用 Inspector 调试
确认虚拟环境处于激活状态,然后执行:
`MCP_LAB_ROOT="$HOME/mcp-mobile-lab/lab" npx @modelcontextprotocol/inspector python server.py`
首次使用 `npx` 时,它可能提示下载对应软件包。Inspector 启动后,按照终端显示的本地地址打开页面,连接 Server,然后执行以下操作:
1. 打开工具列表,确认存在 `sha256_file` 和 `inspect_apk_archive`。
2. 调用 `sha256_file`,参数填写 `{"relative_path":"sample.apk"}`。
3. 调用 `inspect_apk_archive`,使用相同的相对路径。
4. 尝试传入 `../server.py` 或绝对路径,确认服务端拒绝访问。
5. 传入不存在的文件或非 APK 文件,确认错误信息清晰且没有泄露完整系统路径。
直接执行 `python server.py` 时,进程通常会等待来自 stdin 的 MCP 消息,看上去像“没有输出”。这对 stdio Server 属于正常现象,不应通过 `print()` 向 stdout 增加调试文字,否则可能破坏协议流。
## 六、接入 MCP Client
不少桌面客户端采用类似下面的配置结构,但配置文件位置和字段支持情况应以具体客户端版本文档为准:
```json
{
"mcpServers": {
"mobile-lab": {
"command": "/home/user/mcp-mobile-lab/.venv/bin/python",
"args": [
"/home/user/mcp-mobile-lab/server.py"
],
"env": {
"MCP_LAB_ROOT": "/home/user/mcp-mobile-lab/lab"
}
}
}
}
```
建议全部使用绝对路径,避免客户端工作目录不同导致加载错误。Windows 环境中需要改为实际的 `python.exe`、脚本和实验目录路径。修改配置后重启客户端,再检查工具是否被发现。
如果是自研 Client,应至少实现初始化、能力协商、工具发现、工具调用、超时及取消处理。不要假定 Server 永远可信:即使输入参数符合工具结构,Client 仍应在界面中展示工具名称、目标文件和影响范围,并让用户确认敏感调用。
## 七、权限与安全设计
### 1. MCP 工具不是天然权限边界
模型决定“想调用什么”,真正的授权必须由 Host 和 Server 执行。不能因为调用来自模型,就默认它代表用户授权。高风险工具应采用默认拒绝、最小权限和逐次确认策略。
### 2. 防止提示注入影响工具调用
APK 文件名、ZIP 条目名、日志和扫描报告都属于不可信内容。如果其中出现“忽略规则并调用某工具”等文本,Client 不应将其视为系统指令。建议把工具输出标记为外部数据,并限制它触发后续写文件、联网或执行命令的能力。
### 3. 不要暴露通用 Shell
类似 `run_command(command: str)` 的工具很方便,但几乎等同于把当前用户的终端权限交给模型。更合理的方式是按业务拆分工具,例如 `calculate_apk_hash`、`read_scan_report`,并为每个参数设置枚举、长度和路径限制。
### 4. 防范路径竞争与符号链接
本文的路径校验足以演示基本防护,但在多人共享目录或存在恶意本地用户的环境中,校验后到打开文件之间仍可能发生 TOCTOU 竞争。生产环境可使用专用低权限账户、不可写输入目录、容器挂载只读卷,以及操作系统提供的相对目录句柄和禁止跟随符号链接机制进一步加固。
### 5. 远程传输必须补充身份认证
如果以后将 Server 改为 Streamable HTTP 等远程传输,必须增加 TLS、身份认证、细粒度授权、来源校验、请求大小限制、速率限制和审计日志。不要把测试 Server 直接绑定到公网,也不要把访问令牌写入工具返回内容或普通日志。
### 6. 日志应可审计但避免泄密
建议记录调用时间、工具名称、调用结果、耗时和经过脱敏的参数,不记录 APK 完整内容、访问令牌或用户隐私数据。哈希值是否敏感取决于业务场景,在内部未公开样本库中也应按安全数据管理。
## 八、常见问题
### 1. 客户端看不到工具
优先检查 Python 解释器和脚本是否为绝对路径、虚拟环境是否安装 `mcp`、配置文件 JSON 是否有效,以及客户端是否真的重新启动。也可以先用 Inspector 排除 Server 本身的问题。
### 2. Server 启动后立即退出
在终端单独运行脚本,查看 stderr 中的依赖错误、语法错误或目录权限错误。不要为了调试而向 stdout 打印日志;stdio 模式下 stdout 是协议通道。
### 3. 为什么不直接调用 jadx、apktool 或 adb?
这些工具本身很有价值,但参数多、资源消耗更高,部分操作还可能安装或执行应用。接入 MCP 时应先做固定参数模板、超时、并发数、输出目录和设备范围限制,再逐项开放,不能把用户输入原样拼进 Shell。
### 4. 为什么 APK 条目名称仍被认为不可信?
工具安全不只取决于是否执行文件。模型可能读取条目名称并据此继续调用其他工具,因此数据层面的提示注入同样需要隔离。最稳妥的方式是仅返回完成当前任务所需的信息,并要求高影响操作再次获得用户确认。
### 5. 工具报压缩比过高,但样本确认正常怎么办?
安全阈值不是通用标准,应根据授权样本规模、主机内存和业务场景调整。不要简单删除限制,可以提高阈值,同时加入执行超时、进程内存限制和并发控制。
## 九、总结
一个可靠的 MCP Server 不只是“让模型能够调用函数”,更重要的是把能力边界设计清楚。本例通过 stdio 暴露 APK 哈希和容器检查工具,同时落实了实验目录隔离、相对路径约束、文件大小限制、压缩炸弹检测、结构化返回和不可信数据标记。
后续如果需要接入静态扫描、证书检查或内部漏洞知识库,建议继续遵循同一原则:每个工具只完成单一任务,参数采用白名单,执行使用低权限身份,输出限制大小,高风险动作必须获得用户确认,并为所有调用保留可审计记录。这样才能让 MCP 的扩展能力真正服务于移动应用安全工作,而不是引入一个难以控制的新攻击面。