Skip to main content
Glama
README.md
# nsuid-mapper-web

Web page, HTTP API and MCP server for [nsuid-mapper](https://github.com/maixiang-sh/nsuid-mapper),
on Cloudflare Workers at **https://nsuid-mapper.ohneko.com**.

Unofficial project; not affiliated with or endorsed by Nintendo.

| Path              | What                                                                                |
| ----------------- | ----------------------------------------------------------------------------------- |
| `/`               | Web page: map an NSUID or store link, with product details and store links          |
| `/api/v1/map`     | Public HTTP API, mapping only                                                       |
| `/mcp`            | Public MCP server (Streamable HTTP, stateless), one tool: `map_nsuid`               |
| `/docs/`          | API and MCP docs, including install instructions to paste to an agent               |
| `/openapi.json`   | OpenAPI description of the HTTP API                                                 |
| `/llms.txt`       | Summary for AI tools                                                                |
| `/api/web/lookup` | Backend of the web page; the only place product data (Savanna) is used. Not public. |

## How it works

- Mapping comes from nsuid-mapper, installed from GitHub at a pinned commit and imported
  from `nsuid-mapper/source`. Update it with `bun add github:maixiang-sh/nsuid-mapper#<commit>`.
- Results are cached in KV (`MAP_CACHE`): one day when every target mapped, five minutes
  otherwise. Upstream failures are not cached.
- Only cache misses reach Nintendo, and only they count against the per-IP rate limit
  (`API_LIMITER`, about 20 a minute; Cloudflare counts approximately).
- Without `from`, the source store is found by asking the JP, US, GB, HK and KR stores.
- Requests that would reach Nintendo pass a guard: the pause switch, then about 20 a minute
  per IP (429), then about 300 a minute from everyone together (503 `busy`). Cached results
  skip the guard. KV failures (including a used-up daily quota) never fail a request: a read
  counts as a miss and a write is skipped.

## If the service is abused

Pause every new lookup; cached results are still served:

```bash
bun x wrangler deploy --var UPSTREAM_ENABLED:false
```

Resume with `bun run deploy`. The variable can also be changed in the Cloudflare dashboard,
but the next deploy resets it to `wrangler.jsonc`.

| File            | Role                                                       |
| --------------- | ---------------------------------------------------------- |
| `src/index.ts`  | Routes                                                     |
| `src/map.ts`    | Input validation and the mapping cache                     |
| `src/run.ts`    | Mapping as offered by the API and MCP, with error outcomes |
| `src/api.ts`    | HTTP API handler                                           |
| `src/mcp.ts`    | MCP server                                                 |
| `src/detect.ts` | Finding the source store                                   |
| `src/lookup.ts` | Web page backend: input parsing, product details, links    |
| `src/web.ts`    | Web page backend handler                                   |
| `src/limit.ts`  | Pause switch and rate limits                               |
| `src/cache.ts`  | KV access that never fails a request                       |
| `public/`       | Web page, docs page, OpenAPI and llms.txt                  |

## Development

Requires [Bun](https://bun.sh). Deploying needs `CLOUDFLARE_API_TOKEN` and
`CLOUDFLARE_ACCOUNT_ID` in the environment, or `bun x wrangler login`.

```bash
bun install
bun run dev     # local server at http://localhost:8787 (requests reach Nintendo)
bun run check   # typecheck, format check, tests
bun run deploy  # deploy to Cloudflare
```

## License

[MIT](LICENSE)