1. 项目概述:当AI遇上应用商店自动化
最近在跟一个独立开发团队聊天,他们每周要处理十几个不同客户的应用更新,光是上传到苹果的App Store和Google Play这两个平台,手动操作就得花掉大半天。截图、描述、元数据、版本号……每个环节都不能错,错了就得重新排队审核,一来二去,一周的时间就这么耗在重复劳动上了。这让我想起了自己几年前踩过的坑,于是决定把一套已经稳定运行了快两年的自动化方案拿出来聊聊,核心就是用Claude Code(现在更常被称为Claude for Developers)配合一个叫Blitz MCP的工具,来实现应用商店提交的全流程无人值守。
简单来说,这个方案解决的就是“最后一公里”的自动化问题。你的CI/CD流水线可能已经能完美地编译、打包、签名,但生成的.ipa或.aab文件怎么自动、安全、合规地送到苹果和谷歌的服务器上,并填好所有繁琐的表单?这就是Blitz MCP的用武之地。它本质上是一个“模型上下文协议”(Model Context Protocol)服务器,为Claude这类AI编码助手提供了直接操作App Store Connect和Google Play Console API的能力。你不再需要写冗长且易错的脚本去直接调用官方API,而是用自然语言或结构化的指令告诉Claude,让它来生成并执行这些操作代码。
这适合谁呢?首先是像开头提到的外包团队或拥有多个应用矩阵的开发者,重复性工作量大。其次是追求研发效能、希望将发布流程真正纳入DevOps体系的团队。最后,哪怕你只是个独立开发者,学会这个也能让你从机械的点击操作中解放出来,把时间花在更有价值的编码和产品思考上。整个过程,我们追求的是可靠、可审计、以及最重要的——安全,确保自动化的每一步都符合平台规范,不会因为配置错误导致审核被拒或账号风险。
2. 核心思路与工具链选型解析
2.1 为什么是“Claude Code + MCP”而非传统脚本?
在自动化应用商店提交的领域,传统方案无外乎两种:一是使用官方提供的命令行工具,如苹果的altool(已弃用)和xcrun notarytool、xcrun altool的替代方案,以及谷歌的bundletool和fastlane中的supply;二是直接调用App Store Connect API和Google Play Developer API。这两种方式我都深度使用过,但它们共同的门槛在于维护成本。
直接使用命令行工具,你需要记住海量的参数,处理复杂的证书、描述文件路径,并且当苹果或谷歌更新其工具链时,你的脚本很可能需要调整。而直接调用REST API,虽然灵活,但你需要处理OAuth 2.0认证、请求签名(特别是苹果的JWT)、分页、错误重试等底层细节,代码量庞大且脆弱。
MCP(Model Context Protocol)的出现改变了游戏规则。你可以把它理解为一个“翻译器”或“适配器”。它将那些复杂的、需要特定知识(如API签名算法)的操作,封装成一个个简单的“工具”(Tools)暴露出来。Claude Code作为“大脑”,不需要理解苹果JWT如何生成,它只需要知道有一个叫appstore_list_apps的工具可以列出所有应用。当你想列出应用时,Claude会生成调用这个工具的代码。Blitz MCP则实现了这些针对App Store和Google Play的“工具”。
这样做的核心优势:
- 降低认知负担:开发者无需成为苹果/谷歌API专家。你关注业务逻辑(“上传版本”、“更新截图”),Blitz MCP负责技术实现。
- 提升代码可靠性:Blitz MCP封装了最佳实践,比如自动重试机制、合理的速率限制规避,减少了因网络波动或API限制导致的失败。
- 增强安全性:敏感信息(如API密钥、私钥)保存在MCP服务器配置中,而不是硬编码在项目脚本里。Claude生成的代码只是调用本地或远程的MCP服务。
- 未来兼容性:当平台API变更时,通常只需要更新Blitz MCP服务器版本,而不需要修改你所有的自动化脚本。
2.2 工具链深度剖析:Blitz MCP、Claude Code与环境配置
Blitz MCP:这是本方案的核心引擎。它是一个开源的MCP服务器,专门为App Store Connect和Google Play Console集成而构建。它通过环境变量或配置文件读取你的认证信息,然后暴露出诸如appstore_create_version,play_upload_bundle,appstore_submit_for_review等一系列高阶操作工具。它的安装通常通过npm包管理器进行:npm install -g @modelcontextprotocol/server-blitz。你需要为其配置两个核心信息:
- 苹果端:Issuer ID, Key ID, 以及下载的
.p8私钥文件路径。这些需要在App Store Connect中生成一个“App Store Connect API”密钥来获得。 - 谷歌端:一个服务账号的JSON密钥文件。这个服务账号需要在Google Play Console中被授予“发布管理员”或类似权限。
Claude Code / Claude for Developers:这是我们的“驾驶员”。它需要被配置为能够连接到MCP服务器。在Claude Desktop应用中,这通过在claude_desktop_config.json配置文件中添加MCP服务器配置来实现。这样,当你在Claude的编码界面中提出需求时,它就能感知到Blitz MCP提供的工具列表,并据此生成正确的调用代码。它本质上是一个增强了代码生成和理解能力的AI助手,特别适合这种需要组合多个步骤的流程自动化任务。
辅助工具链:
- Fastlane:尽管我们使用Blitz MCP替代了
fastlane deliver和supply的直接调用,但Fastlane生态中的其他组件,如gym(构建)、match(证书管理)仍然极具价值。我们的自动化流程可以是:Fastlane构建并签名 -> 生成产物 -> 触发Python/Node.js脚本(由Claude Code生成)-> 脚本通过Blitz MCP上传并提交。 - GitHub Actions / GitLab CI / Jenkins:作为自动化流程的触发器。你可以在代码仓库打上版本标签(如
v1.2.3)时,触发CI流水线,执行上述包含Blitz MCP调用的发布脚本。 - Python或Node.js:Claude Code生成的调用MCP工具的脚本,通常使用这两种语言,因为它们有成熟的MCP客户端库(如
@modelcontextprotocol/sdkfor Node.js)。
注意:环境隔离至关重要。建议在CI服务器上使用独立的服务账号进行发布操作,与个人开发者账号分开。并且,这些API密钥的权限应遵循最小权限原则,仅授予发布应用所必需的权限。
3. 实战:构建端到端自动化发布流水线
3.1 前期准备与认证配置
自动化的一切始于正确的身份认证。这一步出错,后面所有流程都会失败。
苹果App Store Connect配置:
- 登录 App Store Connect ,进入“用户和访问”->“密钥”页面。
- 点击“+”生成一个新的密钥,妥善保存下载的
.p8私钥文件(它只提供一次)。同时记录下生成的Key ID和Issuer ID。 - 为该密钥分配适当的角色,例如“App Manager”,使其具备创建版本、上传构建、提交审核的权限。
谷歌Google Play Console配置:
- 登录 Google Play Console ,进入你的应用。
- 导航到“设置”->“API访问”。
- 关联一个Google Cloud项目(或新建一个)。
- 在“服务账号”部分,创建或选择一个服务账号,并下载其JSON格式的私钥文件。
- 回到Play Console的“用户和权限”页面,将该服务账号的邮箱添加为具有“发布管理员”权限的用户。
本地开发环境配置Blitz MCP:
# 全局安装Blitz MCP服务器 npm install -g @modelcontextprotocol/server-blitz # 设置环境变量(以macOS/Linux为例,Windows使用set命令) export APPSTORE_ISSUER_ID='你的Issuer ID' export APPSTORE_KEY_ID='你的Key ID' export APPSTORE_PRIVATE_KEY_PATH='/path/to/your/AuthKey_XXX.p8' export PLAY_SERVICE_ACCOUNT_KEY_PATH='/path/to/your/service-account-key.json' # 启动Blitz MCP服务器,指定端口(例如8080) mcp-server-blitz --port 8080启动后,Blitz MCP服务器会在本地8080端口运行,等待来自Claude Code(通过MCP客户端)的指令。
配置Claude Desktop连接MCP:找到Claude Desktop的配置文件(通常在~/Library/Application Support/Claude/claude_desktop_config.json),添加以下配置:
{ "mcpServers": { "blitz": { "command": "npx", "args": [ "-y", "@modelcontextprotocol/server-blitz" ], "env": { "APPSTORE_ISSUER_ID": "你的Issuer ID", "APPSTORE_KEY_ID": "你的Key ID", "APPSTORE_PRIVATE_KEY_PATH": "/path/to/your/AuthKey_XXX.p8", "PLAY_SERVICE_ACCOUNT_KEY_PATH": "/path/to/your/service-account-key.json" } } } }重启Claude Desktop后,Claude就具备了与App Store和Google Play交互的能力。
3.2 核心自动化脚本编写与Claude协作
现在,我们进入核心环节:如何与Claude协作,编写一个自动上传iOS应用到App Store Connect并提交审核的脚本。
第一步:向Claude明确任务在Claude Code的聊天界面中,你可以这样描述需求: “我需要一个Python脚本,使用MCP客户端调用本地的Blitz MCP服务器。脚本需要完成以下功能:1. 列出我的App Store Connect中的所有应用。2. 找到Bundle ID为com.example.myapp的应用。3. 为该应用创建一个新的版本(例如2.1.0)。4. 将本地路径/build/MyApp.ipa的IPA文件上传到这个新版本。5. 最后,提交这个版本进行审核。请生成完整的脚本,并包含错误处理。”
第二步:Claude生成的脚本骨架与解析Claude可能会生成类似下面的代码。这里我加入大量注释,解释每个关键部分:
import asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client # 1. 定义MCP服务器连接参数(与Claude Desktop配置对应) server_params = StdioServerParameters( command="npx", args=["-y", "@modelcontextprotocol/server-blitz"], env={ "APPSTORE_ISSUER_ID": "YOUR_ISSUER_ID", "APPSTORE_KEY_ID": "YOUR_KEY_ID", "APPSTORE_PRIVATE_KEY_PATH": "/path/to/key.p8", "PLAY_SERVICE_ACCOUNT_KEY_PATH": "/path/to/service-account.json" } ) async def automate_appstore_submission(): async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: # 初始化会话,获取服务器提供的工具列表 await session.initialize() # 2. 列出所有应用 print("Listing all apps...") list_result = await session.call_tool("appstore_list_apps", {}) apps = list_result.content # 假设返回的是应用列表 target_app = None for app in apps: # 这里需要根据Blitz MCP返回的实际数据结构来调整,例如 app['bundleId'] if app.get('bundleId') == 'com.example.myapp': target_app = app break if not target_app: raise Exception("Target app not found.") app_id = target_app['id'] print(f"Found app: {target_app['name']} (ID: {app_id})") # 3. 创建新版本 print("Creating new version 2.1.0...") version_args = { "appId": app_id, "versionString": "2.1.0", "platform": "IOS", # 可能是 IOS, TVOS 等 # 可选: copyright, releaseType (SCHEDULED, AFTER_APPROVAL, MANUAL) } version_result = await session.call_tool("appstore_create_version", version_args) version_id = version_result.content['id'] # 4. 上传IPA构建文件 print("Uploading IPA...") # 注意:实际的工具名和参数需要查阅Blitz MCP文档。上传可能分两步:创建构建记录,然后上传文件。 # 这里是一个假设性的调用,真实情况可能更复杂,涉及分块上传。 upload_args = { "appId": app_id, "versionId": version_id, "ipaFilePath": "/build/MyApp.ipa" } upload_result = await session.call_tool("appstore_upload_build", upload_args) build_id = upload_result.content['id'] print(f"Build uploaded successfully. Build ID: {build_id}") # 5. 提交审核 print("Submitting for review...") submit_args = { "appId": app_id, "versionId": version_id, # 可能需要提供审核信息,如出口合规、广告标识符使用等 "exportCompliance": True, "usesIdfa": False } await session.call_tool("appstore_submit_for_review", submit_args) print("Submission successful! The app is now in review.") # 6. (可选)轮询构建处理状态 # 上传后构建需要苹果处理(代码签名、安全检查等),可以添加轮询逻辑等待处理完成再提交。 # print("Waiting for build processing...") # processing = True # while processing: # build_status = await session.call_tool("appstore_get_build_status", {"buildId": build_id}) # if build_status.content['processingState'] == 'VALID': # processing = False # print("Build processed successfully.") # elif build_status.content['processingState'] == 'FAILED': # raise Exception("Build processing failed.") # else: # await asyncio.sleep(30) # 每30秒检查一次 if __name__ == "__main__": asyncio.run(automate_appstore_submission())第三步:脚本的定制与增强Claude生成的代码是一个极佳的起点,但你需要根据实际情况进行调整:
- 错误处理增强:上述脚本的基础错误处理不够健壮。你需要对每个
call_tool调用进行try-except包装,捕获特定异常(如网络超时、API限制、认证失败),并实现重试逻辑(尤其是上传文件)。 - 参数化:将Bundle ID、版本号、IPA文件路径、平台类型等硬编码值改为从命令行参数、环境变量或配置文件读取。这使脚本可复用。
- 状态查询与等待:如注释所示,上传构建后,苹果需要时间处理。最佳实践是先上传,然后轮询构建状态,直到其变为
VALID(或FAILED)后再执行提交审核操作。Blitz MCP应该提供查询构建状态的工具。 - 谷歌Play的集成:流程类似。你需要调用
play_list_apps,play_upload_bundle等工具。注意谷歌Play的版本代码(versionCode)是整数,且必须递增。
3.3 集成到CI/CD流水线
让脚本在本地运行只是第一步,集成到CI/CD才是自动化的终极形态。这里以GitHub Actions为例:
# .github/workflows/release.yml name: Build and Release to App Stores on: push: tags: - 'v*' # 当推送v开头的标签时触发 jobs: build-and-release: runs-on: macos-latest # iOS构建需要macOS环境 steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '18' - name: Install Blitz MCP Server run: npm install -g @modelcontextprotocol/server-blitz - name: Build iOS App (using Fastlane) env: APPSTORE_API_KEY_ISSUER_ID: ${{ secrets.APPSTORE_ISSUER_ID }} APPSTORE_API_KEY_KEY_ID: ${{ secrets.APPSTORE_KEY_ID }} APPSTORE_API_KEY_KEY: ${{ secrets.APPSTORE_PRIVATE_KEY }} MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} run: | bundle install bundle exec fastlane ios build - name: Release to App Store Connect env: # 将.p8密钥内容存入环境变量,脚本中写入临时文件 APPSTORE_PRIVATE_KEY: ${{ secrets.APPSTORE_PRIVATE_KEY }} TARGET_BUNDLE_ID: 'com.example.myapp' IPA_PATH: './output/MyApp.ipa' VERSION_STRING: ${GITHUB_REF#refs/tags/v} # 从标签v1.2.3中提取1.2.3 run: | # 将私钥写入临时文件 echo "$APPSTORE_PRIVATE_KEY" > /tmp/AuthKey.p8 # 执行Claude协助生成的、并经过我们增强的Python脚本 python3 scripts/release_to_appstore.py \ --bundle-id "$TARGET_BUNDLE_ID" \ --ipa-path "$IPA_PATH" \ --version "$VERSION_STRING" \ --issuer-id "${{ secrets.APPSTORE_ISSUER_ID }}" \ --key-id "${{ secrets.APPSTORE_KEY_ID }}" \ --private-key-path /tmp/AuthKey.p8在这个工作流中:
- 触发条件是打上版本标签。
- 使用macOS runner来构建iOS应用(假设使用Fastlane)。
- 在“Release to App Store Connect”这一步,将敏感的API密钥通过GitHub Secrets注入环境变量。
- 执行我们完善的Python脚本,完成从上传到提交的全流程。
4. 避坑指南与实战经验总结
自动化之路从不平坦,尤其是与苹果和谷歌的API打交道。下面是我在实践中总结的几个关键陷阱和应对策略。
4.1 认证与权限的“暗礁”
- 问题:脚本运行时报“Authentication Failed”或“Insufficient Permissions”。
- 排查:
- 苹果:检查
.p8私钥文件内容是否正确、是否已过期(通常不过期,但密钥可能被撤销)。确保Issuer ID、Key ID完全匹配,且密钥在App Store Connect中具有所需应用的足够权限(如“App Manager”)。 - 谷歌:确认服务账号的JSON密钥有效,且在Google Play Console的“用户和权限”中,该服务账号邮箱已被添加并授予了应用级的“发布管理员”角色。仅在Google Cloud IAM中授权是不够的,必须在Play Console内再授权一次。
- 苹果:检查
- 心得:专门为CI/CD创建一个独立的“机器用户”账号或API密钥,并定期审计其权限。避免使用个人开发者账号的主密钥。
4.2 网络与API限流的“缓冲”
- 问题:上传大文件时超时,或频繁收到429(Too Many Requests)错误。
- 策略:
- 实现指数退避重试:对于所有网络请求,特别是上传操作,包装一个重试逻辑。例如,第一次失败后等待2秒重试,第二次失败后等待4秒,以此类推。
- 分块上传:如果Blitz MCP支持,优先使用分块上传大文件,这比单次上传更稳健。
- 尊重速率限制:苹果和谷歌的API都有严格的速率限制。在脚本中,特别是在循环查询构建状态时,主动添加延迟(如
time.sleep(5)),避免触发限流。
- 心得:将“网络不可靠”作为第一原则来设计脚本。每一个对外部API的调用都要假设它可能失败,并准备好优雅的重试和降级方案(例如,失败后发送通知给开发者)。
4.3 数据一致性与状态管理的“迷宫”
- 问题:脚本逻辑是“创建版本->上传构建->提交审核”,但上传构建后,构建可能处于“处理中”状态,此时立即提交审核会失败。
- 解决方案:引入状态机思维。在上传构建后,不要立即提交,而是进入一个轮询等待循环,定期使用
appstore_get_build_status工具检查构建的处理状态。只有当状态变为VALID(或谷歌Play的类似状态)时,才执行下一步的提交操作。同时,要处理FAILED状态并抛出错误。 - 心得:与App Store Connect和Google Play的交互本质上是异步的。你的脚本必须能够处理这种异步性,等待前置操作真正完成后再进行后续操作。一个清晰的流程图对于设计正确的脚本逻辑至关重要。
4.4 元数据与合规性检查的“最后一关”
- 问题:应用审核被拒,原因可能是元数据(截图、描述、关键词)不符合规范,或者新的构建版本触发了某些合规性问题(如出口合规、广告标识符使用声明)。
- 策略:
- 元数据预检:在上传新版本前,可以先用脚本调用
appstore_get_app_info获取当前线上版本的元数据作为基线,确保本次更新不会意外删除或错误修改关键字段。对于需要更新的文本,使用appstore_update_version_metadata等工具进行精准修改。 - 合规性参数化:在提交审核的工具调用中(
appstore_submit_for_review),明确传递exportCompliance、usesIdfa等参数。这些参数的值应该根据你的应用实际情况,通过配置文件或构建参数动态决定,而不是硬编码。
- 元数据预检:在上传新版本前,可以先用脚本调用
- 心得:自动化不是为了“闭着眼睛运行”,而是为了“可靠地重复”。将审核检查清单中的关键项(如隐私政策URL是否更新、新权限说明是否添加)转化为脚本中的验证步骤或配置项,能极大降低审核被拒的风险。
最后一点个人体会:引入Claude Code和Blitz MCP这类工具,最大的价值不是节省了写脚本的时间,而是降低了自动化流程的维护门槛。当苹果明年又推出新的API时,我可能只需要更新一下Blitz MCP的版本,而不用逐行修改我那脆弱的、直接调用requests库的脚本。这种将复杂接口抽象成语义化工具的模式,让开发者能更专注于业务逻辑本身——告诉我“要做什么”,而不是“具体怎么做”。对于需要频繁发布、管理多个应用的团队来说,花一两天时间搭建这样一套体系,长期来看回报是极其丰厚的。刚开始可能会觉得配置繁琐,但一旦跑通,那种只需打一个标签就能坐等应用上线的感觉,会让你觉得所有的投入都是值得的。