Skip to main content
Glama

app-store-connect-mcp

一个 MCP 服务器,用于 Apple 的两个商业 API —— App Store Connect(1,263 个操作)和 App Store Server API / StoreKit 2(30 个操作)—— 通过五个工具实现,私钥存储在 macOS 钥匙串中,具有后果的写入操作需要显式确认。

1,293 operations · 5 tools · key never on disk · verified against the live APIs

为何如此构建

已有多个 App Store Connect MCP 服务器。每个都解决了部分问题;此方案取各者之长,弃其之短。

方式

保留

舍弃

手工封装工具

每个端点一个 MCP 工具

带类型、可发现的参数

70–900 个工具定义,>10 万 token,Apple 发布新版本时立刻过时

代码模式

LLM 编写 JS,服务器 eval 执行

两个工具,约 1k token,全覆盖

在持有签名密钥的进程中执行生成的代码

元工具

search → call 带参数

相同的上下文优势,无需代码执行

—

此服务器采用第三种。覆盖率是 Apple 规范本身的属性,而非取决于某人封装了多少端点;并且模型永远无法在可以修改定价的进程内运行代码。

关于沙箱

代码模式的前提是生成的 JavaScript 在 Node 的 vm 中安全运行。事实并非如此。Node 自己的文档指出 vm 不是安全机制,任何作为全局对象注入的主机对象都会通过其自身的原型链暴露主机领域:

spec.constructor.constructor('return process.env.HOME')()   // → /Users/you

已针对该沙箱的忠实复现进行验证:它返回了主机环境。timeout 选项也无济于事——它仅限制同步执行,因此 async 忙循环会永远运行并耗尽事件循环。

参数化调度获得了相同的覆盖范围和相同的 token 成本,且无需可逃逸的解释器。

Related MCP server: App Store Connect MCP Server

凭据

私钥应存放在钥匙串中。Apple 允许你仅下载一次 .p8 文件,磁盘上的明文副本可能会导致泄露。

ASC_KEY=keychain:my-asc-key          # recommended
ASC_KEY=/path/to/AuthKey.p8          # works, but plaintext
ASC_PRIVATE_KEY='-----BEGIN…'        # discouraged: `ps -E` exposes it

钥匙串条目可以存储裸 PEM,或 base64 编码的 JSON:

{ "issuerID": "…", "keyID": "…", "privateKeyPEM": "-----BEGIN PRIVATE KEY-----\n…" }

推荐使用信封形式:标识符与密钥材料一同传输,因此 ASC_KEY_ID 不会与其命名的密钥不同步——这种不匹配只会表现为不透明的 401 错误。

security add-generic-password -s my-asc-key -a api -w "$(
  jq -nc --arg i "$ISSUER" --arg k "$KEYID" --arg p "$(cat AuthKey.p8)" \
    '{issuerID:$i,keyID:$k,privateKeyPEM:$p}' | base64
)"

安装

git clone https://github.com/abd3lraouf-studios/app-store-connect-mcp
cd app-store-connect-mcp
npm install && npm run build
{
  "mcpServers": {
    "app-store-connect": {
      "command": "node",
      "args": ["/path/to/app-store-connect-mcp/dist/index.js"],
      "env": {
        "ASC_KEY": "keychain:my-asc-key",
        "ASC_BUNDLE_ID": "com.example.app"
      }
    }
  }
}

仅当调用 App Store Server API 时,才需要 ASC_BUNDLE_ID——Apple 拒绝没有 bid 声明的 Server API token。

工具

工具

用途

asc_status

验证凭据,报告可达性和剩余速率限制配额。当任何操作失败时先运行此工具——它可以将错误的密钥与错误的请求区分开来。

asc_search_endpoints

按关键词、方法、标签或风险等级搜索两个 API。返回 operationId 并说明每个操作属于哪个工具。

asc_describe_endpoint

参数、请求体模式(包含真实字段名)、风险等级。

asc_call

读取操作。 路径和查询参数、分页、两个 API。

asc_write

所有更改数据的操作。 确认、dry_run、两个 API。

读取和写入是分开的工具,因为 Claude Code 会忽略标准的 destructiveHint 注解,但会尊重 _meta["anthropic/requiresUserInteraction"]——并且该标志是每个工具独立的。单个调度器无法为每个操作更改此标志。asc_write 携带此标志,因此即使在 bypassPermissions 下,写入操作也会提示用户。这比进程内的防护门(可通过 --no-confirm 关闭)提供了更强的保证。

资源

模型可以通过 @asc: 主动拉取的参考材料:

资源

内容

asc://cookbook

Apple 返回成功响应但实际含义与表面不同的情况——分页、alpha-3 地区代码、被拒绝的 sort、gzip 压缩的报告

asc://enums

所有 90 个枚举字段,从 Apple 的规范自动生成,因此不会过时

asc://risk

每个风险等级的含义及其可逆性

asc://sources

每个 API 描述的来源及时间

asc-response://…

溢出存储——见下文

如果结果太大无法内联返回,不会被截断。列表会修剪到适合的大小,同时说明截断情况及如何缩小请求范围,完整响应会作为资源保留,客户端可以在不消耗上下文的情况下读取。在序列化 JSON 中间截断会使模型得到无法解析的内容;静默截断更糟,因为部分列表会被视为完整列表。

提示词

四个工作流,可通过 /mcp__asc__<name> 使用:

release-readiness · pricing-audit · review-triage · testflight-status

每个工作流链式调用多个请求——用斜杠命令包装一个请求只是同义词,而非工作流——每个工作流都编码了陷阱,例如 sort 在 customerReviews 上会被拒绝,以及审核文本是不可信的输入。

写入安全

HTTP 方法不能很好地反映后果:PATCH /v1/subscriptionPrices 和 PATCH /v1/appInfos/{id} 都是写入操作,但只有前者会改变对客户的收费,并且两者都不会因重复执行而撤销。操作带有风险等级:

等级

数量

含义

READ

797

无变化。

WRITE

238

更改数据。

REVENUE

61

定价、订阅、权益。

DESTRUCTIVE

132

删除操作。

RELEASE

12

构建、提交、发布内容。

ACCESS

12

可访问账户的用户。

INFRASTRUCTURE

11

证书、标识符、回调 URL。

默认情况下,底部五个等级会返回确认令牌而非执行操作。该令牌通过哈希绑定到具体的操作、路径、查询和请求体,因此无法通过低成本调用获取令牌后用于高成本调用。令牌一次性使用,五分钟内有效。

--read-only    block every write        --confirm     confirm every write
--no-confirm   never confirm            (default)     confirm the five tiers above

当客户端支持引导时,asc_write 直接询问用户,显示方法、路径、请求体和等级。否则,它会回退到通过哈希绑定到具体操作、路径、查询和请求体的确认令牌,因此为低成本调用颁发的令牌不能用于高成本调用。声明支持引导但未能提供的客户端会回退,而不是直接通过。dry_run 会报告确切的请求,但不会发送。

传输层

node dist/index.js                       # stdio (default)
node dist/index.js --transport http --http-token "$(openssl rand -hex 32)"

HTTP 绑定到 127.0.0.1,并且没有 bearer token 则拒绝启动。此进程持有可以更改 App Store 定价的密钥;不应在没有认证的情况下监听。绑定到非回环地址会发出警告,最好与 TLS 终止代理或 SSH 隧道配合使用。

与 Apple 保持同步

npm run fetch:specs   # re-download both descriptions
npm run build         # recompile the operation index
npm run verify        # drift check + live calls against both APIs

两个 API 的来源不同,这是必要的:

  • App Store Connect — Apple 发布了一个真实的 OpenAPI 3.0 文档。它被下载并编译成精简索引(360KB,相比于 3.3MB 的规范),以便搜索保持快速,并且仅在描述一个操作时才打开完整文档。

  • App Store Server — Apple 未发布任何 OpenAPI 文档;文档是纯文本形式。权威的机器可读描述是 Apple 自己的客户端 apple/app-store-server-library-node,其中每个端点都是一个直接的 makeRequest 调用。fetch:specs 从该源码的固定发布标签中解析出端点集,而 verify 将其与 src/storekit.ts 中的目录进行差异比较。

该目录中有两个细节与文档暗示的内容相矛盾,并且两者都是关键:

  • 主机是 api.storekit.apple.com / api.storekit-sandbox.apple.com。旧的 api.storekit.itunes.apple.com 名称不再提供此 API。

  • 批量续订扩展状态路径的段顺序为 {productId}/{requestIdentifier}——而不是相反。

验证

npm run verify 是只读操作,并会发起真实调用。上次运行时间:

1. Catalogue drift — src/storekit.ts vs Apple’s client
  ✓ all 30 Apple endpoints present in the catalogue
  ✓ no endpoints in the catalogue that Apple does not define

2. App Store Connect API — live
  ✓ apps_getCollection → 2 apps
  ✓ apps_getInstance / builds / appStoreVersions → HTTP 200
  ✓ pagination walked 3 pages
  ✓ bogus id → structured 404

3. App Store Server API (StoreKit 2) — live
  ✓ storekit token carries bid;  connect token correctly omits it
  ✓ getTransactionInfo / getAllSubscriptionStatuses / getTransactionHistory v2
      → authenticated and routed (Apple errorCode 4000006)
  ✓ getNotificationHistory (30d window) → HTTP 200

14 passed, 0 failed

StoreKit 探测使用故意无效的交易 ID。信号是回复的结构:一个结构化的 Apple errorCode 证明请求已通过身份验证并被路由,而 401 则证明未通过身份验证。

健壮性

  • 超时和重试。 读取操作在 408/429/5xx 错误时重试;写入操作仅在 429 错误时重试,此时 Apple 在处理请求之前已拒绝。写入操作若失败不明确,则报告为不明确且绝不重新发送——重复的 POST 比报告失败更糟糕。

  • 速率限制。 根据文档记录的每小时限制和未记录的每分钟限制进行节流,并根据 Apple 自己的 x-rate-limit 头部进行校正,该头部会考虑共享密钥的其他客户端。x-request-id 会提供给 Apple 支持。

  • 主机锁定。 每个 URL(包括 links.next 分页游标)都会根据 Apple 的三个 API 主机白名单进行检查。游标是服务器提供的输入;盲目跟随会将 bearer token 带到它命名的任何主机。

  • 响应整形。 移除 links 和仅包含链接的 relationships,保留 links.next——在真实价格点列表上,体积减小超过 60%。

  • 生命周期。 stdio 服务器在 stdin 遇到 EOF 和接收到信号时退出,而不是作为持有签名密钥的孤儿进程残留。

已知限制

  • JWS 响应被解码,但未验证。 StoreKit 负载由 Apple 签名到达;验证链需要 Apple 的根证书。解码后的值出现在 *_decoded 字段中,并标记为未验证。未检查签名前,请勿将其视为购买凭证。

  • 风险等级基于方法与路径的模式匹配。 它故意保持谨慎,但在执行写入操作前,请阅读 asc_describe_endpoint,不要仅依赖等级。

  • 钥匙串存储仅限 macOS。 在其他平台上,请使用具有严格权限的文件路径。

  • --no-confirm 完全禁用防护门。它专为 CI 设计;对于交互式代理来说,这是一个糟糕的默认设置。

许可证

MIT

Available Tools

13 tools
asc_analytics_reportA
Read-only

Fetch an analytics report as rows. Walks request → report → instance → every segment, downloads the signed URLs, gunzips the TSV and returns parsed rows. Reading only the first segment — the obvious mistake — yields a plausible subset with nothing marking it partial. Call without report_name to list what exists. Never creates a report request: accessType ONGOING is a standing commitment on the account, not a query.

NameRequiredDescriptionDefault
appYesBundle ID, store name, or numeric Apple ID.
dateNoProcessing date, YYYY-MM-DD. Omit for the newest instance.
max_rowsNoDefault 5000, cap 50000.
list_onlyNoList available reports without fetching one.
granularityNo
report_nameNoOmit to list the available reports.
asc_availability_setA
Destructive

Turn an app’s App Store availability on or off, territory by territory. Apple offers no bulk endpoint — appAvailabilities answers 403 "does not allow UPDATE" once the record exists — so this is one PATCH per territory, up to 175 of them. Territories already in the target state are skipped, a failure part-way does not abandon the rest, and every territory is re-read afterwards and compared against the request, because a loop that finishes is not evidence that the store matches what was asked for. Cannot set availableInNewTerritories: that is web-UI only.

NameRequiredDescriptionDefault
appYesBundle ID, store name, or numeric Apple ID.
stateYeson = downloadable, off = removed from sale.
confirmNoConfirmation token from a previous gated call. Not needed when the user is prompted directly.
territoriesNoISO alpha-3 codes, e.g. ["USA","GBR"]. Omit for every territory the app has.
asc_callA
Read-only

Read from Apple. Handles both APIs, path and query parameters, and pagination. READ operations only — anything that changes data goes through asc_write. A short list may just be one page: if the result says truncated, say so rather than treating it as complete.

NameRequiredDescriptionDefault
queryNoQuery parameters. Arrays are sent as repeated keys. Filters look like {"filter[bundleId]": "com.x"}; sparse fieldsets like {"fields[apps]": "name,bundleId"}; "limit" caps at 200. Territories are ISO alpha-3 (USA, not US) — the two-letter form returns an empty list, not an error.
paginateNoFollow links.next and concatenate.
max_pagesNoDefault 10, cap 50.
environmentNoStoreKit only; overrides the server default for this call.
operationIdYesFrom asc_search_endpoints.
path_paramsNoValues for {placeholders}, e.g. {"id": "6763390896"}.
asc_describe_endpointA
Read-only

Full detail for one operation: every parameter, the request body schema with real field names, and its risk tier. Call this before any write.

NameRequiredDescriptionDefault
operationIdYes
asc_listing_screenshotsA
Read-only

List the screenshot sets and screenshots on a version, with each asset’s delivery state. Uses included resources to turn what is otherwise a request per locale per set into a handful.

NameRequiredDescriptionDefault
appYesBundle ID, store name, or numeric Apple ID.
localeNoe.g. en-US. Omit to inspect the primary locale only.
versionNoVersion string. Omit for the newest.
asc_preflight_versionA
Read-only

Answer "can this version actually be submitted?" across build state, export compliance, every localisation, screenshots, review contact details and any open submission. Returns GO or NO-GO with each gap naming the operation that fixes it. Catches the states that stall a release without explaining themselves — a build still PROCESSING, or an unanswered export-compliance question that parks the version at WAITING_FOR_EXPORT_COMPLIANCE.

NameRequiredDescriptionDefault
appYesBundle ID, store name, or numeric Apple ID.
versionNoVersion string. Omit for the newest.
asc_pricing_getA
Read-only

Read subscription pricing across every territory for an app, with the currency resolved and territories grouped by price so outliers stand out. Collapses a four-hop walk and ~175 lookups into a handful of requests. The currency lives on the territory, not the price row, which is why reading prices by hand tends to produce ambiguous numbers.

NameRequiredDescriptionDefault
appYesBundle ID, store name, or numeric Apple ID.
territoryNoISO alpha-3, e.g. USA. Omit for every territory.
subscriptionNoProduct ID, name or id. Omit for all of them.
asc_pricing_setA
Destructive

Change a subscription price. preserve_current_price is required and has no default: true keeps existing subscribers on what they pay now, false moves them at their next renewal. Apple defaults it to false, so leaving it implicit silently re-prices your existing base. Find a price point id with asc_call subscriptions_pricePoints_getToManyRelated.

NameRequiredDescriptionDefault
appYesBundle ID, store name, or numeric Apple ID.
confirmNoConfirmation token from a previous gated call. Not needed when the user is prompted directly.
territoryNoISO alpha-3. Omit to apply to the price point’s own territory.
start_dateNoYYYY-MM-DD. Omit to apply as soon as Apple allows.
subscriptionYesProduct ID, name or id.
price_point_idYesFrom the subscription’s price points.
preserve_current_priceYesREQUIRED. true = existing subscribers keep their price. false = they are moved to the new one.
asc_search_endpointsA
Read-only

Find operations across the App Store Connect API (1263 operations, v4.4.1) and the App Store Server API / StoreKit 2 (30). Search by keyword, method, tag or risk tier; returns operationIds for asc_call and asc_write. Start here rather than guessing a path — some resources are not where the URL pattern implies.

NameRequiredDescriptionDefault
apiNoDefault "both".
tagNoApp Store Connect tag, e.g. "Apps", "Builds".
riskNo
limitNoDefault 25, max 200.
queryNoKeyword, e.g. "subscription price", "builds", "refund".
methodNo
asc_statusA
Read-only

Verify credentials and report what this server can reach, including the remaining rate-limit budget. Run this first when anything fails — it distinguishes a bad key from a bad request.

NameRequiredDescriptionDefault

No parameters

asc_upload_iap_screenshotA
Destructive

Upload the App Store review screenshot for an in-app purchase, performing Apple’s reserve → upload → commit sequence. This is a different resource from a listing screenshot and is the usual reason an in-app purchase stays in MISSING_METADATA when everything else about it is complete — it is the one required field with no text to type. Apple rejects non-standard aspect ratios: a window capture at 1706x1610 fails IMAGE_BAD_ASPECT_RATIO, while 2880x1800 passes. Validation is asynchronous, so a rejected image commits cleanly and only then reports FAILED.

NameRequiredDescriptionDefault
iapYesNumeric in-app purchase id. asc_pricing_get lists them.
confirmNoConfirmation token from a previous gated call. Not needed when the user is prompted directly.
dry_runNoReport what would be sent, including the checksum, without sending it.
file_nameNoName to store it under. Defaults to the file’s own name.
file_pathYesAbsolute path to the image on this machine.
asc_upload_screenshotA
Destructive

Upload a screenshot image, performing Apple’s full reserve → upload → commit sequence: reserve a slot, PUT each byte range to Apple’s asset host with the headers Apple supplies, then commit with an MD5 checksum. The raw API cannot do this in one call, and a wrong checksum or offset does not error — it leaves the asset stuck, which looks like nothing happened.

NameRequiredDescriptionDefault
confirmNoConfirmation token from a previous gated call. Not needed when the user is prompted directly.
dry_runNoReport what would be sent, including the checksum, without sending it.
file_nameNoName to store it under. Defaults to the file’s own name.
file_pathYesAbsolute path to the image on this machine.
screenshot_set_idYesFrom asc_listing_screenshots.
asc_writeA
Destructive

Change something in App Store Connect: create, update or delete. Consequential tiers (REVENUE, DESTRUCTIVE, INFRASTRUCTURE, ACCESS, RELEASE) ask the user to confirm before anything is sent. Use dry_run first when you are unsure what a call will do — it validates and reports the exact request without sending it. Read asc_describe_endpoint first to get the body schema right.

NameRequiredDescriptionDefault
bodyNoJSON:API request body. For binary uploads, a base64 string.
queryNoQuery parameters. Arrays are sent as repeated keys. Filters look like {"filter[bundleId]": "com.x"}; sparse fieldsets like {"fields[apps]": "name,bundleId"}; "limit" caps at 200. Territories are ISO alpha-3 (USA, not US) — the two-letter form returns an empty list, not an error.
confirmNoConfirmation token from a previous gated call. Not needed when the user is prompted directly.
dry_runNoValidate and report the request that would be sent, without sending it.
environmentNoStoreKit only; overrides the server default for this call.
operationIdYesFrom asc_search_endpoints.
path_paramsNoValues for {placeholders}, e.g. {"id": "6763390896"}.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 13 tool updatesv1.2.0
    • First observedasc_analytics_report
    • First observedasc_availability_set
    • First observedasc_call
    • First observedasc_describe_endpoint
    • First observedasc_listing_screenshots
    • First observedasc_preflight_version
    • First observedasc_pricing_get
    • First observedasc_pricing_set
    • First observedasc_search_endpoints
    • First observedasc_status
    • First observedasc_upload_iap_screenshot
    • First observedasc_upload_screenshot
    • First observedasc_write

TDQS

A4.2/5.0

Scored across 13 tools

Disambiguation5/5

Each tool has a clearly distinct role: generic read/write, endpoint discovery, pricing, submission readiness, screenshot uploads, analytics, and availability. Even the two upload tools are explicitly differentiated by resource type, and the specialized tools are described as collapsing multi-step workflows rather than duplicating asc_call/asc_write.

Naming Consistency3/5

All tools share the asc_ prefix and use snake_case, but the pattern after the prefix varies: some are verb-noun (asc_search_endpoints, asc_upload_screenshot), some are noun-verb (asc_pricing_get, asc_availability_set), and some are bare or noun-like (asc_call, asc_status, asc_analytics_report). The set remains readable, but the naming convention is not uniform.

Tool Count5/5

Thirteen tools is well within the ideal range for a cohesive server. The generic asc_call and asc_write cover the huge underlying API surface, while the specialized tools earn their place by handling complex, multi-step workflows that would otherwise be error-prone.

Completeness5/5

The generic read/write tools plus endpoint discovery and schema description provide full lifecycle coverage for the App Store Connect API, with no obvious dead ends. The high-level tools deliberately cover the most painful real-world flows like pricing, submission readiness, screenshot uploads, analytics, and availability.

Maintenance

ActivityStale
ResponsivenessNo recent community issues

Related MCP Connectors

Related MCP Servers