store-preflight-mcp
Scans iOS/macOS code for Apple App Store policy compliance, detecting required reason API declarations, privacy manifest issues, and rejection codes.
Integrates with GitHub via SARIF upload to render inline PR annotations for policy violations detected during CI scans.
Scans Android code for Google Play policy compliance, covering restricted permissions, data safety categories, and foreground service requirements.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@store-preflight-mcpScan my project for app store rejection risks."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
store-preflight-mcp
Your agent just wrote code that will get your app rejected. It has no idea.
An MCP server that maps API usage โ required declaration โ dated policy, so a coding agent finds out at write time instead of from an App Store Connect email three days after the PR merged.
$ store-preflight scan . --date 2026-07-15
๐ UserDefaults requires a declared reason in PrivacyInfo.xcprivacy
ITMS-91053
No PrivacyInfo.xcprivacy exists anywhere in this project. Code touches
UserDefaults APIs, so App Store Connect will refuse the upload.
โ ios/RejectedApp/AnalyticsModule.swift:7 private let defaults = UserDefaults.standard
fix: PrivacyInfo.xcprivacy โ NSPrivacyAccessedAPICategoryUserDefaults
โ failing: 7 finding(s) at or above blockerThat's the whole product. An agent adds an analytics module, touches
UserDefaults, and nothing in the world tells it that Apple has required a
CA92.1 entry in a file it has never heard of since 1 May 2024. The binary
is rejected days later, by which point nobody remembers which PR did it.
Doc MCPs describe APIs. None of them map an API to the declaration it obliges, or to the date that obligation acquired teeth.
Why dates are the whole design
This is the sibling of mobile-docs-mcp,
and it reuses its core insight. There, a symbol isn't "real" โ it's real within a
version range (since / deprecated / removed). Here, a rule isn't "true" โ
it's true within a date window:
PolicyWindow(
announced = "2023-06-05", # WWDC23
warning_from = "2024-03-13", # email only, upload still succeeds
enforced_from = "2024-05-01", # hard block
)PolicyWindow.status_on(date) is the direct analogue of
VersionRange.status_at(version). Same model, different axis โ because store
policy ships on deadlines, not releases.
This isn't decoration. It's what makes the tool usable:
Severity follows the calendar. A blocker that doesn't bite until October is reported as a warning in July, automatically. Nobody hand-edits severities as deadlines pass.
You can scan the future.
--date 2026-10-28tells you what breaks when a deadline lands, before it lands.The tests don't rot. Every assertion is pinned to a fixed date, so the suite doesn't silently change verdict when a deadline passes.
The same unchanged repo, scanned on two dates:
$ store-preflight scan . --date 2026-07-15
โน๏ธ Geofencing is no longer an approved use case for a location foreground service
โ ๏ธ READ_CONTACTS requires a declaration for apps targeting Android 17+
$ store-preflight scan . --date 2026-10-28
โ ๏ธ Geofencing is no longer an approved use case for a location foreground service
๐ READ_CONTACTS requires a declaration for apps targeting Android 17+Not one byte of that repo changed. That failure mode is invisible to every other tool in the toolchain โ a linter diffs your code, and your code is fine.
(Why does READ_CONTACTS escalate to a blocker while geofencing stops at a
warning? Because the permission string is either in your manifest or it isn't,
whereas whether geofencing is what justifies your location FGS depends on a
form you filed in Play Console, which isn't in the repo. See
On being trustworthy.)
Related MCP server: @squirex.dev/mcp-server
Install
uvx store-preflight-mcp # MCP server (stdio)
pipx install store-preflight-mcp # CLI tooClaude Code / any MCP client:
{
"mcpServers": {
"store-preflight": {
"command": "uvx",
"args": ["store-preflight-mcp"]
}
}
}Zero non-mcp runtime dependencies. No crawl, no API key, no network. The
ruleset ships as Python and the whole scan is local.
Tools
Tool | Use |
| Scan a repo. What will be rejected, where, and why. |
| Before writing: "I'm about to use |
| The exact key + reason code + paste-ready snippet. |
| Paste |
| The dated calendar. What's enforced, what lands next. |
check_api_declaration is the one that changes agent behaviour โ it's cheap
enough to call before writing the code, which is the only time the answer is free.
> check_api_declaration("UserDefaults", store="apple")
๐ BLOCKER ยท status enforced โ enforced since 2024-05-01 (806 days ago)
You must declare: NSPrivacyAccessedAPICategoryUserDefaults in PrivacyInfo.xcprivacy
Valid values:
ยท CA92.1 โ Read/write info accessible only to the app itself...
ยท 1C8F.1 โ ...only to apps, extensions and App Clips in the same App Group...
ยท C56D.1 โ Third-party SDK only: wrapper function around UserDefaults...
ยท AC6B.1 โ Read com.apple.configuration.managed (MDM managed app config)...CI
- run: pipx install store-preflight-mcp
- run: store-preflight scan . --format sarif -o preflight.sarif
- uses: github/codeql-action/upload-sarif@v3
with: { sarif_file: preflight.sarif }SARIF means findings render as inline PR annotations, on the line that caused
them. --fail-on blocker (the default) exits non-zero, so a rejection becomes a
red check instead of an email.
Coverage
App Store
All 5 required-reason API categories, all 17 reason codes, each with Apple's verbatim meaning โ
UserDefaults,FileTimestamp,SystemBootTime,DiskSpace,ActiveKeyboards17
Info.plistpurpose strings, incl. the iOS 17 calendar split (NSCalendarsFullAccessUsageDescription) most projects still missPrivacy manifest validity: ITMS-91054 / 91055 / 91056
All 86 privacy-impacting SDKs + npm/pub โ framework aliasing
NSPrivacyTrackingwithoutNSPrivacyTrackingDomainsThe Xcode 26 / iOS 26 SDK upload gate (since 2026-04-28)
All 6 ITMS rejection codes decoded
Google Play
Restricted permissions:
QUERY_ALL_PACKAGES,MANAGE_EXTERNAL_STORAGE, SMS/Call Log,ACCESS_BACKGROUND_LOCATION,REQUEST_INSTALL_PACKAGES,USE_EXACT_ALARM,USE_FULL_SCREEN_INTENT, accessibility, photo/video14 data safety categories with their data types
All 14 foreground service types (including
mediaProcessing, which most lists miss), each with its required permission and runtime prerequisiteAdvertising ID, including the auto-merged-from-
play-services-adstrapNew for 2026: the Contacts policy (2026-10-28) and the geofencing withdrawal (2026-08-26)
Languages: Swift, Objective-C, Kotlin, Java, JS/TS, Dart โ plus
Info.plist, PrivacyInfo.xcprivacy, AndroidManifest.xml, package.json,
pubspec.yaml, Podfile. React Native, Flutter and native all work; no build
required.
On being trustworthy
A preflight tool that cries wolf gets --ignored within a week, and then it may
as well not exist. Three deliberate concessions:
Findings carry their own epistemics. Apple's required-reason rules are
mechanically checkable: the symbol is in your binary or it isn't. Play's data
safety form is not โ it keys on transmission off-device, not on holding a
permission. Google says plainly that on-device processing needs no disclosure. So
READ_CONTACTS does not imply a Contacts disclosure, and every such mapping
is marked heuristic: it raises a question, and a heuristic finding can never
fail your build (enforced by a test).
The target API level is not hardcoded. When this ruleset was built
(2026-07-15), Google's own timeline page was internally inconsistent โ metadata
dated May 2026, content still describing the August 2025 / API 35 deadline โ and
no API 36 announcement existed on any Google property, despite third-party
blogs confidently asserting one. So the rule is encoded ("within one year of
the latest major Android release") along with last_verified and a
needs-live-check flag. A tool that states a fake deadline confidently is worse
than one that says "verify this".
The SDK check is a prompt, not a verdict. Apple's requirement attaches when an app is new or an update adds a listed SDK โ not to every app that contains Firebase. And "any SDKs that repackage those on the list" are included, so exact matching under-detects. Both facts make a confident verdict impossible, so it ships as a warning that says why.
What we do not concede: comments and string literals are stripped before symbol
matching (with line numbers preserved), node_modules/Pods are never walked,
and tools:node="remove" is honoured โ so the correct way to drop the
auto-merged AD_ID permission isn't reported as the violation.
Verify it
python smoke_test.py70 assertions, no network. Four fixture repos: one that gets rejected, the same app fixed, a manifest that is wrong on its own terms, and one whose code never changes while the policy date moves under it. The suite also pins the facts that are easy to get wrong:
โ exactly 5 required-reason categories
โ exactly 17 reason codes
โ 86 SDKs on the commonly-used list
โ getattrlist maps to BOTH FileTimestamp and DiskSpace (not 1:1)
โ ITMS-91054 title is 'Invalid API category declaration'
โ 14 foreground service types incl. mediaProcessing
โ target SDK is a rule, not a hardcoded literal
โ no heuristic finding is ever a blockerThose aren't padding โ each one is a mistake that was actually made and caught
during the build. getattrlist really does oblige two separate declarations. The
SDK list really is 86, not 87, because Apple publishes "BoringSSL / openssl_grpc"
as one entry.
See RULES.md for provenance โ every rule's source, and the 18 traps that make this domain hostile to naive implementations (Apple's docs are JS-rendered shells; there's a JSON feed that isn't).
Licence
MIT
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/asif786ka/store-preflight-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server