Skip to main content
Glama
fikrifirmanf

mobile-unslop-mcp

by fikrifirmanf

mobile-unslop-mcp

Stop AI-written mobile code from crashing on users' phones or getting rejected at review: skill + MCP verifier for iOS (Swift/SwiftUI), Android (Kotlin/Compose), React Native and Flutter.

npm License: MIT MCP

An agent skill plus an MCP server in one package. The skill tells your coding agent what slop to avoid; the MCP tools then deterministically verify the result, so the agent can't just claim it's done.

Works with Claude Code, Claude Desktop, Codex and Antigravity.

What it catches

Rule

Severity

Catches

SWIFT_FORCE_TRY

ERROR

try! crashes the app on any thrown error. Use do/catch or try? with a fallback.

SWIFT_FORCE_CAST

ERROR

as! crashes when the cast fails. Use as? with guard/if-let.

SWIFT_FORCE_UNWRAP

WARN

Force unwrap ! crashes on nil. Use guard let / if let / ?? default.

SWIFT_MAIN_SYNC

ERROR

DispatchQueue.main.sync deadlocks when called from the main thread. Use @MainActor / await MainActor.run / main.async.

SWIFT_FIXED_FONT_SIZE

WARN

Fixed font size ignores Dynamic Type. Use text styles (.font(.body)) or scale with relativeTo / UIFontMetrics.

SWIFT_DEVICE_SIZE_HARDCODE

ERROR

Layout hardcoded to one device width (or UIScreen.main, deprecated for multi-window). Use flexible frames, GeometryReader or containerRelativeFrame.

SWIFT_ICON_BUTTON_UNLABELLED

ERROR

Icon-only Button without an accessibility label: VoiceOver reads the SF Symbol name. Use Label("Delete", systemImage:) or .accessibilityLabel.

SWIFTUI_ONAPPEAR_TASK

WARN

.onAppear { Task { } } leaks work after the view disappears. Use .task { }, which is cancelled automatically.

SWIFT_ANYVIEW

WARN

AnyView erases identity and defeats SwiftUI diffing. Use @ViewBuilder, generics or some View.

SWIFT_PRINT_LOGGING

WARN

print() logging ships to production and cannot be filtered. Use os.Logger with a subsystem/category.

KOTLIN_GLOBALSCOPE

ERROR

GlobalScope coroutines outlive the screen and leak. Use viewModelScope / lifecycleScope / an injected scope.

BLOCKING_SLEEP

ERROR

Thread.sleep/runBlocking blocks the calling thread (ANR on main). Use delay() inside a coroutine.

KOTLIN_NOT_NULL_ASSERTION

WARN

!! throws NullPointerException. Use ?.let, ?: or requireNotNull with a message.

COMPOSE_ICON_BUTTON_NULL_DESCRIPTION

ERROR

IconButton whose icon has contentDescription = null is unlabeled for TalkBack. Describe the action.

COMPOSE_STATE_WITHOUT_REMEMBER

ERROR

mutableStateOf without remember resets on every recomposition. Wrap in remember { } (or rememberSaveable).

COMPOSE_HARDCODED_STRING

WARN

Hardcoded UI string can't be translated. Use stringResource(R.string.x).

ANDROID_LOG_DEBUG

WARN

Debug logging ships in release builds. Use Timber with a release tree, or strip with R8 rules.

ANDROID_CLEARTEXT_TRAFFIC

ERROR

Cleartext HTTP enabled app-wide. Use HTTPS; scope exceptions to specific debug domains in network_security_config.

ANDROID_DEBUGGABLE

ERROR

android:debuggable hardcoded in the manifest ships debuggable release builds. Let the build type set it.

ANDROID_ALLOW_BACKUP

WARN

allowBackup without backup rules copies tokens and databases to cloud/adb backups. Add dataExtractionRules or disable.

IOS_ATS_ARBITRARY_LOADS

ERROR

App Transport Security disabled for all domains (App Review asks for justification). Use HTTPS or a per-domain NSExceptionDomains entry.

IOS_VAGUE_PURPOSE_STRING

ERROR

Empty or vague permission purpose string: App Review rejects these (guideline 5.1.1). Say what the feature does with the data.

INSECURE_TOKEN_STORAGE

ERROR

Credential stored in plain preferences (UserDefaults/SharedPreferences/AsyncStorage). Use Keychain, EncryptedSharedPreferences/Keystore, expo-secure-store or flutter_secure_storage.

RN_SCROLLVIEW_MAP

WARN

ScrollView + .map renders every row at once. Use FlatList/FlashList for lists.

RN_TOUCHABLE_UNLABELLED

WARN

Icon-only touchable without accessibilityLabel/accessibilityRole is invisible to screen readers.

FLUTTER_SETSTATE_AFTER_AWAIT

WARN

setState after an await without a mounted check throws if the widget was disposed. Add if (!mounted) return;.

FLUTTER_PRINT

WARN

print() ships to release logs. Use debugPrint or package:logging.

verify_app_manifest cross-checks the platform APIs your code uses against Info.plist purpose strings (and Expo app.json), PrivacyInfo.xcprivacy, and AndroidManifest permissions and android:exported flags: the mistakes that crash on first use or fail App Review / Play Console.

Rules marked ERROR make the audit return status: "FAIL"; WARN is reported but does not fail.

Related MCP server: platao

Quick start

1. Add the MCP server

# Claude Code
claude mcp add mobile-unslop -s user -- npx -y mobile-unslop-mcp

# Codex
codex mcp add mobile-unslop -- npx -y mobile-unslop-mcp

Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json) and Antigravity (~/.gemini/config/mcp_config.json):

{
  "mcpServers": {
    "mobile-unslop": {
      "command": "npx",
      "args": ["-y", "mobile-unslop-mcp"]
    }
  }
}

Restart the app afterwards.

2. Install the bundled skill

npx -y mobile-unslop-mcp install-skills        # clients that are installed
npx -y mobile-unslop-mcp install-skills --all  # every location

Client

Location

Claude Code

~/.claude/skills/

Codex

~/.codex/skills/

Antigravity

~/.gemini/config/skills/ (and ~/.gemini/antigravity/skills/ if present)

Claude Desktop has no skills folder, so it receives the rules through the MCP prompt and the load_*_rules tool.

MCP surface

Kind

Name

Purpose

Prompt

mobile-unslop

The full skill text, for GUI clients

Tool

load_mobile_unslop_rules

Returns the full mobile-unslop ruleset. Call before writing code if the mobile-unslop skill is not already loaded.

Tool

audit_mobile_slop

Deterministically scans Swift, Kotlin/Java, Dart, React Native and manifest files for mobile slop: crash operators (try!, as!, !, !!), main-thread deadlocks and blocking sleeps, GlobalScope, missing remember, Dynamic Type and device-size hardcodes, unlabelled icon buttons, insecure token storage, cleartext traffic, ATS disabled, vague permission strings and release logging. Accepts files or directories; with no files it audits the git working-tree changes. Suppress a verified false positive with a unslop-ignore RULE_ID -- reason comment on or above the line.

Tool

verify_app_manifest

Cross-checks platform APIs used in code against Info.plist purpose strings (and Expo app.json), PrivacyInfo.xcprivacy and AndroidManifest permissions/exported flags. Catches crash-on-first-use and App Review / Play Console rejections. Run after adding any camera, location, photos, mic, contacts, Bluetooth, health, notification or network feature.

Tool

verify_mobile_build

Verifies the app compiles. mode 'syntax' (default, fast): swiftc -parse on given Swift files, dart analyze, tsc for React Native. mode 'full': swift build / xcodebuild (simulator, no signing) and ./gradlew compileDebugSources; can take minutes. Missing toolchains are SKIPPED.

File paths resolve against the server's working directory and cannot escape it. Directories honour .gitignore and skip node_modules, build output and minified files.

CLI and CI

The same audit runs without an agent. It exits 1 on any ERROR, so it works as a pre-commit hook or CI step:

npx -y mobile-unslop-mcp audit              # git working-tree changes
npx -y mobile-unslop-mcp audit src app      # files or directories
npx -y mobile-unslop-mcp audit src --json   # machine-readable
npx -y mobile-unslop-mcp rules              # list every rule

Suppressing a false positive

The checks are regex and structure based: fast, offline and predictable, but heuristic. For a verified false positive, add a comment on the line or the line above, with a reason:

// unslop-ignore SWIFT_FORCE_TRY -- why this is safe here

Suppressions without a reason are reported as SUPPRESSION_WITHOUT_REASON errors, and every honoured suppression is listed in the audit output so reviewers can see it.

How it works

  1. The agent loads the skill and writes code under its constraints.

  2. Before finishing, the skill requires the audit and verification tools.

  3. On FAIL, the agent fixes every finding and re-runs until PASS. Missing toolchains are reported as SKIPPED, never as passing.

The unslop family

All share the engine in unslop-core.

Development

git clone https://github.com/fikrifirmanf/mobile-unslop-mcp
cd mobile-unslop-mcp && npm install
npm test                   # every rule is checked against its good/bad examples
node src/server.mjs        # speaks MCP over stdio

License

MIT © Fikri Firman Fadilah

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables coding agents to run deterministic completeness audits on code before declaring 'done'—catching unwired modules, placebo tests, swallowed errors, and stubs via AST analysis, entirely offline and free.
    MIT
  • A
    license
    B
    quality
    B
    maintenance
    Enables AI agents to run deterministic, fully local static security and compliance audits of code, configurations, and dependencies through 32 MCP tools covering 24 vulnerability categories. It also detects the project stack, generates risk reports and checklists, suggests stack-aware remediations, and re-validates fixes to confirm vulnerabilities were eliminated.
    32
    1
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI coding assistants to scan an iOS, Android, Flutter, React Native or Expo project for App Store and Google Play rejection risks, returning deterministic findings that cite the guideline section, link to the official Apple or Google page and explain how to fix each problem. Nothing leaves the machine and no AI is used in the checks.
    11 npm
    MIT