Skip to content

MCP Server

The AppStoreCat MCP (Model Context Protocol) server gives AI tools like Claude Code direct access to app intelligence data.

Package@appstorecat/mcp on npm
Transportstdio (spawned as a local process)
AuthSanctum bearer token via env var
Tools32 tools (28 read-only + 4 write), Swagger-strict, chain-first
Claude Code (stdio) → MCP Server (local) → Laravel API (local or remote)
auth:sanctum

The MCP server does not run in Docker. It is a local Node.js process that Claude Code spawns over stdio.

Terminal window
claude mcp add appstorecat \
-e APPSTORECAT_API_URL=https://server.appstore.cat/api/v1 \
-e APPSTORECAT_API_TOKEN=your-token \
-- npx -y @appstorecat/mcp

Add to .claude/settings.json or the project’s .mcp.json:

{
"mcpServers": {
"appstorecat": {
"command": "npx",
"args": ["-y", "@appstorecat/mcp"],
"env": {
"APPSTORECAT_API_URL": "https://server.appstore.cat/api/v1",
"APPSTORECAT_API_TOKEN": "your-token-here"
}
}
}
}
VariableRequiredDefaultDescription
APPSTORECAT_API_TOKENYesSanctum API token (created from the web UI)
APPSTORECAT_API_URLNohttp://localhost:7460/api/v1API base URL

Every tool follows two rules enforced at the schema level:

  • Swagger-strict — each tool’s zod input mirrors the Swagger parameter list exactly. Nothing invented, nothing dropped. Integer fields reject strings, enums reject free-form text, dates enforce YYYY-MM-DD.
  • Chain-first — responses are passed through as-is (app_id, external_id, version_id, category_id, publisher.external_id are never stripped). Each tool description ends with a “use with: {tool_a}, {tool_b}” hint so the caller can plan multi-step lookups.

The 32 tools split as 28 read-only + 4 write. Read-only tools (everything below except the ones marked ✏️) carry readOnlyHint: true, so MCP clients render them with the safe-to-call indicator.

The 4 write tools — track_app, untrack_app, add_competitor, remove_competitor — carry readOnlyHint: false. Destructive operations (untrack_app, remove_competitor) additionally carry destructiveHint: true. Idempotent ones carry idempotentHint: true. Claude Code surfaces these hints to the user and asks for confirmation before invoking write tools (unless the user has explicitly allowlisted them).

A typical chained-write flow:

# 1. Track your own parent app
track_app(platform: "ios", external_id: "6446901002") # Threads
→ 204 No Content
# 2. Add a competitor by store id — no need to track it first.
# The server creates the competitor's app row on demand and links it,
# WITHOUT adding it to your `list_tracked_apps` view.
add_competitor(
platform: "ios",
external_id: "6446901002",
competitor_external_id: "835599320", # TikTok
relationship: "direct"
)
→ returns the new AppCompetitor row

The competitor’s app row is auto-registered if it doesn’t exist yet, but it is not added to the caller’s user_apps watchlist. So list_tracked_apps keeps showing only the apps you actually chose to track, while reports, charts, and change feeds still see the competitors via the app_competitors link.

If you happen to already know the competitor’s internal numeric id (e.g. you tracked it earlier), you can pass competitor_app_id instead of competitor_external_id. Both forms produce the same row.

ToolDescription
list_categoriesStore categories (App Store + Google Play). Feeds category_id into get_charts, browse_screenshots, browse_icons.
list_countriesSupported countries (ISO-2 codes). Feeds country_code into any location-aware tool.
ToolDescription
list_tracked_appsApps tracked by the authenticated user. Returns internal id and {platform, external_id} for chaining.
track_app ✏️Add {platform, external_id} to the user’s watchlist. Resolves and creates the app from the store if it doesn’t exist yet. Triggers an automatic sync.
untrack_app ✏️Remove the app from the user’s watchlist (and any competitor relationships involving it). Idempotent.
search_store_appsKeyword search against a store (term, platform, country_code, exclude_external_ids[]).
get_appFull app metadata: publisher, category, versions, rating, unavailable_countries, competitors (when tracked).
get_app_listingStore listing for a country_code + locale (title, subtitle, description, screenshots, whats_new, version_id).
get_app_sync_statusSync pipeline status for the app.
get_app_rankingsRank positions across charts (filterable by date, collection).
ToolDescription
list_app_competitorsCompetitors of a specific app.
list_all_competitorsAll tracked competitor groups {parent, competitors[]}.
add_competitor ✏️Add a competitor to a tracked app. Pass competitor_external_id (preferred — auto-registers the app row without touching your watchlist) or the legacy competitor_app_id. Optional relationship: direct, indirect, aspiration (default direct).
remove_competitor ✏️Remove a competitor relationship. competitor_id is the relationship row id from list_app_competitors. Idempotent.
ToolDescription
list_app_changesStore listing changes for tracked apps (filter by field, platform, search, internal app_id; paginated).
list_competitor_changesSame shape, scoped to competitor apps.
ToolDescription
get_chartsTop charts (top_free / top_paid / top_grossing) for a country_code, optional category_id.
ToolDescription
get_rating_summaryCurrent rating + rating count + histogram for an app.
get_rating_historyDaily rating series (days 1–90, default 30).
get_rating_country_breakdownRating breakdown by country (iOS only).
ToolDescription
get_app_keywordsKeyword density for an app listing (locale, ngram 1–3, sort, order, per_page, page; optional version_id).
compare_app_keywordsSide-by-side keyword comparison across up to 5 apps (app_ids[] internal ids, version_ids map).
ToolDescription
search_publishersPublisher search across stores (term, platform, country_code).
list_user_publishersPublishers of the user’s tracked apps.
get_publisherPublisher detail with tracked apps owned by the caller.
get_publisher_store_appsFull store catalog for a publisher (includes is_tracked flag per app).
ToolDescription
browse_screenshotsPaginated screenshot feed across tracked apps (filter by platform, category_id, search).
browse_iconsPaginated icon feed across tracked apps.
ToolDescription
get_dashboardDashboard summary — app counts, recent changes. recent_changes[].app_id chains into list_app_changes.
Terminal window
make mcp-install # Install dependencies
make mcp-build # Compile TypeScript
make mcp-dev # Run in dev mode (tsx watch)
mcp/
├── src/
│ ├── index.ts # Entry point — server init + stdio transport
│ ├── client.ts # HTTP client (fetch + bearer auth, array/object query serializers, path builder)
│ ├── register.ts # Registers all tool modules
│ └── tools/
│ ├── _schemas.ts # Shared zod primitives (Platform, ExternalId, DateStr, Ngram, …)
│ ├── apps.ts # list_tracked_apps, search_store_apps, get_app, get_app_listing, get_app_sync_status, get_app_rankings
│ ├── changes.ts # list_app_changes, list_competitor_changes
│ ├── charts.ts # get_charts
│ ├── competitors.ts # list_app_competitors, list_all_competitors
│ ├── dashboard.ts # get_dashboard
│ ├── explorer.ts # browse_screenshots, browse_icons
│ ├── keywords.ts # get_app_keywords, compare_app_keywords
│ ├── publishers.ts # search_publishers, list_user_publishers, get_publisher, get_publisher_store_apps
│ ├── ratings.ts # get_rating_summary, get_rating_history, get_rating_country_breakdown
│ └── reference.ts # list_categories, list_countries
├── package.json
└── tsconfig.json

The MCP package is part of the release pipeline:

Terminal window
make release v=1.2.0
# → Builds Docker images
# → Updates mcp/package.json version
# → npm publish @appstorecat/mcp
# → git tag + push