Metadata-Version: 2.5
Name: iwym-assets-mcp
Version: 0.1.0
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) |
| `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}` | Keeps the row and venue codes |

Re-enable a staged row with `PATCH /api/v1/assets/{id}` `{"enabled": true}` (no MCP tool; use the HTTP API or extend the server). `auto_exec` is not touched.

### 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).
- Code belongs to another asset, or kind/oracle/anchor differ: HTTP 409, nothing written. `error.details.conflicts` lists `{venue, code, asset_id}`.

## 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.

## 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.
