Metadata-Version: 2.4
Name: kranked-mcp
Version: 0.2.2
Summary: MCP server exposing App Store keyword intelligence — difficulty, popularity, and live rank — to any MCP client. The GUI-free companion to the Kranked ASO app.
Project-URL: Homepage, https://github.com/akoskomuves/kranked
Author-email: Akos Komuves <akos@tallpoppy.xyz>
License: MIT
License-File: LICENSE
Keywords: app-store,app-store-optimization,aso,ios,keywords,mcp
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.10
Requires-Dist: httpx<1,>=0.27
Requires-Dist: mcp<3,>=2
Requires-Dist: uvicorn[standard]>=0.31
Description-Content-Type: text/markdown

# Kranked MCP

An [MCP](https://modelcontextprotocol.io) server that exposes **App Store keyword
intelligence** — keyword difficulty, popularity, and live rank — as tools any MCP
client (Claude, etc.) can call. It's the headless companion to the
[Kranked](https://github.com/akoskomuves/kranked) ASO app: same scoring, no GUI.

Stateless and zero-config: every tool is a live call to Apple's public endpoints (the
iTunes Search API and search-hints). **No database, no API keys.** Runs locally via `uvx`
or as a hosted server.

Speaks MCP **2026-07-28** (Python SDK v2), and stays backwards compatible with 2025-era
clients from the same server. Era is selected per request from the `MCP-Protocol-Version`
header, so over stdio — which has no headers — the server answers the 2025 `initialize`
handshake. Nothing to configure either way; clients negotiate it themselves.

## Tools (free / open core)

| Tool | What it answers |
|---|---|
| `search_apps` | Find apps (and their `app_id`) matching a term |
| `check_keyword` | **One-shot report:** difficulty + popularity + KEI + competitors, and your app's rank |
| `keyword_difficulty` | How hard a keyword is to rank for (0–100), with the top-10 competitors |
| `keyword_popularity` | How searched a keyword is (suggest-based 20/50/80) |
| `keyword_suggestions` | Apple's autocomplete hints for a seed term |

All tools take a two-letter `country` (default `us`).

> **Premium (hosted):** `popular_keywords` — top most-searched keywords by category with
> *real* Apple Search Ads popularity (0–100) — is available on the hosted service, not in
> this open-source package.

### How the scores work

- **Difficulty (0–100)** — analyzes the top-10 ranking apps' review volume and rating
  quality. More established competitors = harder. Labeled Very Easy → Very Hard.
- **Popularity (20/50/80)** — whether Apple auto-suggests the term (exact / related /
  neither). For real ASA popularity numbers, use `popular_keywords`.
- **KEI** — Keyword Efficiency Index = popularity / difficulty. Higher is a better bet.

## Install

On PyPI — nothing to clone or build. `uvx` fetches and runs it on demand. Needs Python
3.10+ and [uv](https://docs.astral.sh/uv/) (`brew install uv`). Add to your MCP client
config:

```json
{
  "mcpServers": {
    "kranked": {
      "command": "uvx",
      "args": ["kranked-mcp"]
    }
  }
}
```

For Claude Code: `claude mcp add kranked -- uvx kranked-mcp`

### Source

The sdist on PyPI carries the full source and the test suite — it's on the
[Download files](https://pypi.org/project/kranked-mcp/#files) tab. Unpack it and
`uv sync`, then point your client at that directory:

```json
{
  "mcpServers": {
    "kranked": {
      "command": "uv",
      "args": ["--directory", "/absolute/path/to/kranked-mcp", "run", "kranked-mcp"]
    }
  }
}
```

Run the tests — they're offline (pure scoring plus server wiring driven through the real
ASGI app), so they don't hit Apple and can't flake on rate limiting:

```bash
uv run pytest
```

## Self-hosting (remote MCP)

Kranked runs in two modes from the same code:

| Mode | Transport | Use | Command |
|---|---|---|---|
| **Local** | stdio | one user, on your machine | `kranked-mcp` |
| **Hosted** | streamable-HTTP at `/mcp` | shared server, many users | `kranked-mcp-serve` |

The hosted server is stateless, so it scales horizontally — no sticky routing, no session
affinity. Since MCP 2026-07-28 that is the protocol's own model rather than an opt-in: there
is no `initialize` handshake and no session id, and clients negotiate up front with
`server/discover` instead. Deploy the included `Dockerfile` to any container host (Railway,
Fly, Render, …); it reads `PORT` from the environment and exposes `GET /health` for liveness
checks.

`server/discover` and `tools/list` carry a one-hour public cache hint (`ttlMs` /
`cacheScope`), so clients can hold the tool list instead of re-listing it every session.

### Deploy to Railway

The repo ships a `railway.toml` (Dockerfile build, `/health` healthcheck). To deploy:

```bash
# one-time
npm i -g @railway/cli && railway login

# from the repo root
railway init            # create/select a project
railway up              # build the Dockerfile and deploy
railway domain          # get a public https URL
```

Or connect the GitHub repo in the Railway dashboard — it picks up `railway.toml` automatically.
Set `KRANKED_CACHE_TTL` in the service's Variables if you want a longer/shorter cache.

Then point a client at the URL:

```bash
claude mcp add --transport http kranked https://your-app.up.railway.app/mcp
```

### Config

| Env var | Default | Purpose |
|---|---|---|
| `PORT` / `HOST` | `8000` / `0.0.0.0` | bind address |
| `KRANKED_CACHE_TTL` | `3600` | seconds to cache upstream responses (`0` disables) |
| `KRANKED_CACHE_MAX` | `2000` | max cached entries |

**Caching is not optional for a hosted deployment.** All users' requests leave from one IP,
so without it you hit Apple's per-IP throttle immediately. Difficulty/popularity change
slowly, so cached results stay useful for hours. For multiple instances, move the cache to Redis.

Setting `KRANKED_API_KEY` gates every endpoint except `GET /health` behind a bearer token;
unauthenticated callers get a 401. Leave it unset and the server is open, which is what you
want for a local stdio run. Usage metering for a paid tier is still roadmap.

## Rate limiting

The iTunes API throttles unauthenticated callers per IP with a 403/429 and a non-JSON
body. The server serializes requests through a global throttle and retries throttles
with exponential backoff + jitter, so it degrades gracefully instead of surfacing bogus
decoding errors.

## License

MIT © Akos Komuves
