内容
<div align="center">
# 🩺 iOS Ship Doctor
**在提交之前捕获应用商店拒绝。**
一个 [MCP](https://modelcontextprotocol.io) 服务器,让您的 AI 助手 — Claude Code、Gemini CLI、Codex、Cursor、Copilot、Windsurf、Zed — 诊断您的 iOS 应用为何会被拒绝 — 隐私清单、缺失的权限字符串、未列出的 SDK、残留的测试凭证 — 然后修复它们。当 Apple 确实拒绝您时,它会拉回拒绝并将其映射到一个简单的修复。
[](https://github.com/menansali/ios-ship-doctor/actions/workflows/ci.yml)
[](./LICENSE)
[](https://nodejs.org)
[](https://modelcontextprotocol.io)
<br/>
<img src="./assets/demo.svg" alt="Ship Doctor preflight output" width="720"/>
</div>
---
## 为什么
苹果的审核流程会悄悄地拒绝构建,因为您在 Xcode 中看不到某些问题:
- 代码中使用了必需原因 API,但未在 `PrivacyInfo.xcprivacy` 中声明
- 使用了权限,但没有 `NS…UsageDescription` 字符串(立即崩溃 + 拒绝)
- 第三方 SDK 在苹果的列表中发货,但没有自己的隐私清单
- `Info.plist` 中留有占位符测试凭证(例如 Google 的示例 AdMob ID)
每个问题都会导致您提交一个循环 — 数小时或数天的往返。Ship Doctor 在几秒钟内检查所有这些问题,并准确告诉您需要更改的内容。
> 现有的应用商店 MCP 是薄 API 包装器,它们会提交您的应用。Ship Doctor 是告诉您 **为什么它不会通过** 的工具。
## 快速入门
无需安装 — `npx` 获取并运行:
```bash
npx -y ios-ship-doctor-mcp preflight /path/to/your/app
```
然后将其与您使用的任何助手集成。这是一个标准的 stdio MCP 服务器 — 它 **不是** Claude 特有的,可以与任何 MCP 客户端一起使用。让它为您打印确切的配置:
```bash
npx -y ios-ship-doctor-mcp config # 每个客户端
npx -y ios-ship-doctor-mcp config gemini # 其中一个
```
<details>
<summary>或从源代码克隆和构建</summary>
```bash
git clone https://github.com/menansali/ios-ship-doctor.git
cd ios-ship-doctor
npm install && npm run build
npm link # 暴露 `ios-ship-doctor-mcp` 命令
```
</details>
开箱即用支持:`claude`、`gemini`、`codex`、`cursor`、`vscode`、`windsurf`、`zed`、`generic`。
| 客户端 | 配置文件 | 根键 |
|---|---|---|
| Claude Code | `claude mcp add ios-ship-doctor -- npx -y ios-ship-doctor-mcp` | `mcpServers` |
| Gemini CLI | `~/.gemini/settings.json`(或 `gemini mcp add …`) | `mcpServers` |
| OpenAI Codex CLI | `~/.codex/config.toml` | `[mcp_servers.…]` ⚠️ 下划线 |
| Cursor | `~/.cursor/mcp.json` 或 `.cursor/mcp.json` | `mcpServers` |
| VS Code (Copilot) | `.vscode/mcp.json` | `servers` ⚠️ 不是 `mcpServers` |
| Windsurf | `~/.codeium/windsurf/mcp_config.json` | `mcpServers` |
| Zed | Zed `settings.json` | `context_servers` ⚠️ `command.path` |
| 其他 | — | `mcpServers` |
生成器为您处理的两个陷阱:Codex 静默忽略 `mcp-servers`(必须是 `mcp_servers`),而打印的 `node` 路径避免了版本固定的 Homebrew/nvm 路径,这些路径在下一次升级时可能会损坏。
然后直接提问:
> **`/path/to/my-app` 是否准备好提交到应用商店?**
## 示例
```
🩺 Ship Doctor 预检 — /Users/you/Desktop/MyApp
裁决:未准备好 — 1 个常见导致拒绝的问题。
(1 个错误,1 个警告,6 个通过)
❌ [credential-traps] Google AdMob 测试应用 ID 在 Info.plist 中
找到占位符/测试值 "ca-app-pub-3940256099942544"。
↳ MyApp/Info.plist
💡 将 GADApplicationIdentifier 替换为您的实际 ca-app-pub-… ID。
⚠️ [export-compliance] Info.plist 中缺少 ITSAppUsesNonExemptEncryption
没有此密钥,应用商店连接会在每次提交时提示加密。
💡 如果您只使用 HTTPS/TLS,请添加 <key>ITSAppUsesNonExemptEncryption</key><false/>
```
## 工具
### 预检 — 本地,无凭证
| 工具 | 它能捕获什么 |
|------|-----------------|
| `preflight` | 运行以下所有检查 → 单个 **READY / NOT READY** 判决 |
| `scan_privacy_manifest` | 使用但未声明的必需原因 API,无效原因代码,缺少清单 |
| `check_usage_descriptions` | 使用相机/位置/照片/麦克风/跟踪但没有 `NS…UsageDescription` |
| `audit_dependencies` | 苹果列出的 SDK 发货时没有 `PrivacyInfo.xcprivacy` |
| `check_credential_traps` | Info.plist 中的占位符/公共测试凭证 |
| `check_legal_links` | 付费墙上缺少隐私政策/使用条款(EULA)链接和应用商店描述(3.1.2) |
| `check_account_requirements` | 没有演示帐户用于应用审核(2.1),没有应用内帐户删除(5.1.1(v)),没有 Sign in with Apple 的社交登录(4.8) |
| `check_external_payments` | 数字内容使用 Stripe/PayPal/Paddle 但没有 StoreKit(3.1.1) |
| `check_background_modes` | 应用实际上从未实现的 `UIBackgroundModes` 条目(2.5.4) |
| `check_placeholder_content` | Lorem ipsum,Stripe 测试密钥,`YOUR_API_KEY`,example.com 死链接,模板应用名称(2.1) |
| `generate_privacy_manifest` | 编写一个有效的 `PrivacyInfo.xcprivacy`,涵盖每个检测到的 API |
| `autofix` | 自动应用安全修复;报告其他修复作为手动跟进 |
`preflight` 还检查 **导出合规性**、**应用传输安全**、**应用图标存在**、**禁止 API** (`UIWebView`)、**启动屏幕**、**版本/构建健全性** 和 **部署目标**。依赖项扫描涵盖 **CocoaPods 和 Swift Package Manager**。
理解的项目布局:经典 `Info.plist` 项目、**具有 `GENERATE_INFOPLIST_FILE` 和无 plist 文件的现代项目** (从 `INFOPLIST_KEY_*` 构建设置中读取密钥)、`ios/` 子目录和 monorepos,其中 `.xcodeproj` 位于一级或两级以下。
### 它不检查的内容
Ship Doctor 读取项目文件。它无法看到大多数拒绝实际涉及的内容:
- **崩溃和不完整的特性(2.1)** — 最常见的拒绝;需要运行应用
- **设计质量(4.0)** 和 **垃圾邮件/重复(4.3)** — 人类判断
- **元数据准确性(2.3)** — 截图是否与实际应用匹配
- **隐私标签准确性(5.1.1)** — 声明的数据收集是否与 SDK 行为匹配
- 任何 **依赖项** 所做的事情;源代码扫描仅限于第一方
干净的运行意味着“没有自动化检查触发”,而不是“这将被批准”。
### 拒绝恢复 — 需要 App Store Connect API 密钥
| 工具 | 它做什么 |
|------|--------------|
| `asc_list_apps` | 列出您的应用(id,名称,bundle id) |
| `asc_get_rejections` | 拉取最近的拒绝,映射审查指南编号 → 摘要 + 修复 |
| `asc_check_submission` | 预检的元数据部分:实际填写的演示凭证,审查说明,所需的截图集 |
| `explain_guideline` | 解释任何审查指南编号(离线,无需密钥) |
<details>
<summary><b>启用拒绝恢复</b></summary>
在 **App Store Connect → 用户和访问权限 → 集成 → App Store Connect API** 处创建密钥,下载 `.p8` 文件,并将环境变量添加到 MCP 配置中:
```json
{
"mcpServers": {
"ios-ship-doctor": {
"command": "ios-ship-doctor-mcp",
"env": {
"ASC_KEY_ID": "XXXXXXXXXX",
"ASC_ISSUER_ID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"ASC_PRIVATE_KEY_PATH": "/absolute/path/to/AuthKey_XXXXXXXXXX.p8"
}
}
}
}
```
`.p8` 文件从未进入仓库 — 它停留在您的机器上,并在运行时读取。
</details>
## 手动配置
如果您不想使用 `npm link`,请直接将您的客户端指向构建文件(此格式适用于 Claude Code、Gemini CLI、Cursor、Windsurf 和大多数其他客户端):
```json
{
"mcpServers": {
"ios-ship-doctor": {
"command": "node",
"args": ["/absolute/path/to/ios-ship-doctor/dist/index.js"]
}
}
}
```
任何工具中的 `projectPath` 都可以是您的仓库根目录(带有 `ios/` 文件夹)或 `ios/` 目录本身。
## 在 CI 中使用(无需助手)
相同的二进制文件作为一次性命令运行,在阻止问题上退出非零:
```bash
npx -y ios-ship-doctor-mcp preflight /path/to/app # 可读
npx -y ios-ship-doctor-mcp preflight /path/to/app --json # 可机读
```
将 [`examples/preflight-gate.yml`](./examples/preflight-gate.yml) 复制到您的应用仓库的 `.github/workflows/` 中,以 **阻止将失败应用商店审核的 PR**。
## 它是如何工作的
- **必需原因 API** — 扫描第一方源代码以获取苹果记录的 API 签名,然后与您声明的 `PrivacyInfo.xcprivacy` 进行差异比较。
- **App Store Connect 身份验证** — 使用 Node 内置的 `crypto`(无 `jsonwebtoken` 依赖项)签署的 ES256 JWT,符合苹果要求的 JOSE(IEEE-P1363)格式。
- **默认安全** — 除 `generate_privacy_manifest` 外,所有内容均为只读,`write=true`。
## 路线图
- [x] 自动修复工具(修补 `Info.plist`,生成隐私清单)
- [x] 更多预检检查:启动屏幕,版本健全性,部署目标
- [x] SwiftPM 依赖项扫描
- [x] CLI + CI 模式
- [ ] 权限健全性(推送,应用组,关联域)
- [ ] 隐私营养标签与 SDK 数据收集的交叉检查
- [ ] 从拒绝中起草审查员回复
## 贡献
欢迎提出问题和 PR。通常,添加检查意味着在 `src/knowledge.ts` 中添加一行,然后在 `src/scanner.ts` 中添加一个小函数。
## 许可证
[MIT](./LICENSE) © menansali