Metadata-Version: 2.5
Name: iwym-assets-mcp
Version: 0.1.2
Summary: MCP server for the iwym asset-universe maintenance API
Author: cyetstar
License-Expression: MIT
Keywords: assets,iwym,mcp
Requires-Python: >=3.11
Requires-Dist: httpx>=0.27.0
Requires-Dist: mcp>=1.9.0
Description-Content-Type: text/markdown

# iwym-assets-mcp

stdio MCP server that lets a Grok Bot maintain the iwym **asset whitelist** (`assets` + `asset_venue_codes`). It wraps the operator HTTP API. It does not open exchange orders and it does not turn on `auto_exec`.

The bot is expected to verify **one asset at a time** (1000SHIB vs SHIB, the same ticker on two venues, Kraken XBT, leveraged tokens) and then write. Catalog diff is the read side of that loop.

## Requirements

- Python 3.11+
- [uv](https://docs.astral.sh/uv/)
- A running iwym API (`IWYM_API_BASE_URL`)
- An operator API token from **Settings → System → OPERATOR API** (paste it as `IWYM_OPERATOR_TOKEN` here)

## 1. API token

Log into the iwym console and open **Settings → System**. The **OPERATOR API** panel shows whether a token is set and when it was last rotated.

- **Generate** creates a token when none is stored.
- **Reset** rotates it. The previous Bearer stops working immediately.
- The plaintext is shown **once**. Copy it into `IWYM_OPERATOR_TOKEN`. It is not written to `.env` on the API, and the API stores only a SHA-256 digest.

The token acts as the operator who generated it. It has the same power as that logged-in operator. Cookie login is unchanged. This Bearer works in any `APP_ENV` and does not need a CSRF cookie.

`AUTH_ALLOW_BEARER` is only for short-lived JWTs in development. Do not use it for this MCP.

## 2. Connect (Cursor / Grok Bot)

From a local checkout:

```json
{
  "mcpServers": {
    "iwym-assets": {
      "command": "uv",
      "args": [
        "--directory",
        "/ABS/PATH/iwym-next/mcp/iwym-assets",
        "run",
        "iwym-assets-mcp"
      ],
      "env": {
        "IWYM_API_BASE_URL": "http://127.0.0.1:8000",
        "IWYM_OPERATOR_TOKEN": "paste-from-settings-system-operator-api"
      }
    }
  }
}
```

`IWYM_API_BASE_URL` is the API origin only (no `/api/v1` suffix).

```bash
cd mcp/iwym-assets
uv sync
export IWYM_API_BASE_URL=http://127.0.0.1:8000
export IWYM_OPERATOR_TOKEN=paste-from-settings-system-operator-api
uv run iwym-assets-mcp
```

stdio waits for the host. Missing env vars exit 1 with a stderr hint.

## 3. Tools

| Tool | HTTP | Notes |
|------|------|--------|
| `list_assets` | `GET /api/v1/assets` | Includes disabled rows |
| `get_asset` | `GET /api/v1/assets/{id}` | |
| `catalog_diff` | `GET /api/v1/assets/catalog-diffs` | `venue` optional. Omit it to scan every seeded ticker venue. `include_orphans` defaults false |
| `create_or_upsert_asset` | `PUT /api/v1/assets/{id}` | Create-or-attach. **`enabled` defaults to false** on this tool (the HTTP body default is true, matching Settings create). Does **not** rewrite scalars on an existing row |
| `update_asset` | `PATCH /api/v1/assets/{id}` | Change `kind` / `enabled` / `display_symbol` / `oracle_feed_type` / `anchor_asset_id` on an existing row. Omitted fields unchanged |
| `set_enabled` | `PATCH /api/v1/assets/{id}` | Toggle Radar whitelist only (`enabled` true/false). Use after `soft_disable_asset` to turn a row back on |
| `add_venue_code` | `POST /api/v1/assets/{id}/venue-codes` | 409 if the code is already taken |
| `soft_disable_asset` | `DELETE /api/v1/assets/{id}` | Sets `enabled=false`; keeps the row and venue codes |

`auto_exec` is not touched.

### Asset `kind` values

| kind | Meaning |
|------|---------|
| `spot_crypto` | Normal CEX crypto base (Radar scan candidate when enabled) |
| `stable` | Stablecoin quote (USDT/USDC/…); not a funding scan base |
| `wrapped` | Wrapped / pegged asset priced off an anchor (e.g. tBTC) |
| `leveraged_token` | CEX leveraged token (3L/3S, UP/DOWN, BULL/BEAR); keep `enabled=false` for Radar |
| `equity` | Stock / equity-like ticker (UVXY, XLE, …); keep `enabled=false` for Radar |

Example — classify stock tickers and drop them from the scan set:

```text
update_asset(asset_id="uvxy", kind="equity", enabled=false)
update_asset(asset_id="xle", kind="equity", enabled=false)
```

Or two calls: `update_asset(..., kind="equity")` then `set_enabled(..., enabled=false)`.

### catalog_diff

Spot and perp legs only. Unique **base** codes, not `BTCUSDT` rows. Tradable instruments only.

Each entry:

- `status`: `missing` (no `(venue, code)` row), `disabled` (mapped, asset `enabled=false`), `orphan` (whitelist code absent from the catalog; only when `include_orphans=true` and that venue's spot and perp reads both succeeded)
- `suggested_asset_id`: lowercase slug, or null when the code cannot be an id (`1000SHIB`)
- `already_mapped_asset_id` / `mapped_asset_enabled`
- `market_types`: `spot` and/or `perp`
- `alias_note`: catalog folds (`XBT`→`BTC`, `XDG`→`DOGE`), multiplier prefixes, leveraged-token suffixes

`errors[]` means that market was not read. Do not treat orphans as complete for a venue listed there.

### create_or_upsert_asset

One asset per call. Pass `venue` + `code`, or `venue_codes_json` as `[{"venue":"binance","code":"SHIB"}]`.

- Asset missing: create it (tool default `enabled=false`) and attach codes that are free. Response HTTP 201, `created: true`.
- Asset exists and kind/oracle/anchor match: attach free codes. Codes already on this asset are unchanged. HTTP 200. Scalar fields are **not** updated (`enabled` stays as stored) — use `update_asset` / `set_enabled`. `kind=spot_crypto` also matches a stored `leveraged_token` or `equity` row so a retry can attach codes without rewriting the product class.
- Code belongs to another asset, or kind/oracle/anchor differ: HTTP 409, nothing written. `error.details.conflicts` lists `{venue, code, asset_id}`.

### update_asset / set_enabled

`update_asset` maps to `PATCH /api/v1/assets/{id}`. Pass only the fields to change. `kind` must be one of the values in the table above.

`set_enabled(asset_id, enabled)` is the narrow form for the Radar whitelist flag (including re-enabling after `soft_disable_asset`).

## 4. Suggested bot loop

1. `catalog_diff(venue="binance")`.
2. Read one `missing` entry. If `alias_note` mentions a multiplier, leverage suffix, or folded alias, stop and ask.
3. `get_asset(suggested_asset_id)` when the suggestion is non-null.
4. `create_or_upsert_asset` with `enabled=false` and that one venue code.
5. On 409, stop. Do not pick a new id to steal the code.
6. Leave the row disabled until a human wants it in the Radar scan set.
7. If the ticker is a leveraged token or equity (UVXY, XLE, …), `update_asset` with `kind=leveraged_token` or `kind=equity` and `enabled=false` (do not rely on create_or_upsert to rewrite kind).

## 5. Security

- Never commit `IWYM_OPERATOR_TOKEN`. The API does not read `OPERATOR_API_TOKEN` from the environment.
- The token is not a trading key. It can still change the whitelist.
- Settings UI CRUD (`POST /assets`, PATCH, venue codes, contract paste) is unchanged.
