Metadata-Version: 2.5
Name: limelijourney-mcp
Version: 0.1.0
Summary: LimeliJourney MCP server — tenant-scoped tools over the key-authed PartnerApi /v1 (API key) or the Cognito AdminApi/AiApi (token).
Author: LimeliJourney
License: Proprietary
Keywords: affiliate,attribution,limelijourney,mcp
Requires-Python: >=3.10
Requires-Dist: mcp>=1.2.0
Requires-Dist: pyyaml>=6.0
Description-Content-Type: text/markdown

# LimeliJourney MCP server (spec 008)

A **tenant-scoped MCP server** that exposes the LimeliJourney AdminApi **and the Track B
AiApi** to AI assistants (Claude Desktop, Claude Code, any MCP client) as typed tools —
`list_/get_/create_/update_` per entity plus `report_*` and the AI-operator surfaces
(`list_tenant_ai_fraud_cases`, `list_tenant_ai_prospects`, `list_tenant_ai_decisions`,
`list_tenant_ai_autonomy`, `list_tenant_ai_sending_domains`, `list_tenant_ai_report`, …).
The tool surface is **generated from [`docs/openapi.yaml`](../docs/openapi.yaml)** (spec 007),
so it tracks the API automatically.

Authenticate with a **revocable API key** (`llj_live_…`) over the versioned `/v1` surface
(**KEY mode**, recommended for clients) or a **Cognito id token** against the AdminApi/AiApi
(**TOKEN mode**, unchanged). See [Two authentication modes](#two-authentication-modes).
Install in one command: `uvx limelijourney-mcp` (see [Install](#install-one-command)).

**Why it's different.** Competitor affiliate-tracker MCPs (where they exist) are read-only.
Ours exposes the full governed **read** surface *and* can trigger the exact same **proposals**
the dashboard does — fraud-case triage, prospect invite/dismiss, sending-domain attestation,
autonomy, policy — each running the server-side governance spine (autonomy lattice, audit,
reversibility). Those writes stay **gated** behind `LIMELI_ALLOW_DESTRUCTIVE=1`; the reads do not.

## Two authentication modes

**KEY mode (recommended)** — authenticate with a **revocable API key** (`llj_live_…`,
minted in the dashboard) against the versioned, key-authed **`/v1`** surface. This is the
mode to hand to a client: it is revocable, scoped, and never expires mid-session like a
Cognito token. Enable it by setting `LIMELI_API_KEY`.

**TOKEN mode (unchanged)** — forward a short-lived **Cognito id token** (the dashboard
login token) to the AdminApi + AiApi. Used automatically when `LIMELI_API_KEY` is unset.

### How it stays safe (the boundary)
- This process is a **thin client**. It holds **no database credentials and no tenant
  logic**. It forwards your credential on every request; the server enforces isolation —
  `limeli_tenancy` (Cognito) or `limeli_api_keys` (the `/v1` key surface).
- **`tenant_id` is never a tool argument** and is never sent in a request body. In KEY mode
  the tenant comes **only** from the API key's row, server-side.
- **Safe by default:** only authenticated, non-deprecated operations are exposed.
  `DELETE`, the bulk `backfill/*` imports, **and the Track B AI write/propose tools**
  (`/tenant/ai/*` → `/v1/ai/<doc>` `PUT` — e.g. `update_tenant_ai_fraud_case`,
  `update_tenant_ai_prospect`, `update_tenant_ai_autonomy`) are **off** unless you set
  `LIMELI_ALLOW_DESTRUCTIVE=1`. The read surface (incl. every `/tenant/ai/*` / `/v1/ai`
  GET) is always on. **This client flag is a convenience, NOT the security boundary** —
  the API enforces scopes (`write:ai` / `write:affiliates` for a key; the role check for a
  token). A read-only key cannot write even with the flag on.
- Super-admin tenant targeting (`X-Tenant-Id` via `LIMELI_TENANT_ID`) is **TOKEN mode only**.

### Which tools are available
- **TOKEN mode:** the full generated AdminApi + AiApi surface (~276 read tools by default).
- **KEY mode:** only tools with a `/v1` equivalent are advertised — the entity/report tools
  **`list_offers`, `get_offer`, `list_affiliates`, `create_affiliate`, `list_campaigns`,
  `get_campaign`, `report_clicks`, `report_conversions`** (→ PartnerApi `/v1/*`) and the
  **AI-operator tools** `list_/update_tenant_ai_*` (→ AiApi `/v1/ai/<doc>`). Any other tool
  has no `/v1` equivalent; it is hidden from the list and, if called, returns a clear
  `not_available_with_api_key` error (never a silent 404) telling you to use a Cognito token.

## Configure

### KEY mode (API key over `/v1`)
| Env var | Required | Meaning |
|---------|----------|---------|
| `LIMELI_API_KEY` | yes | The partner API key (`llj_live_…`). Setting it selects KEY mode. Sent as `X-Api-Key`. |
| `LIMELI_V1_BASE` | yes | PartnerApi `/v1` invoke URL (entity/report tools), e.g. `https://<id>.execute-api.us-west-2.amazonaws.com/prod` |
| `LIMELI_V1_AI_BASE` | no* | Host for the `/v1/ai/<doc>` AI-operator tools — the **AiApi** host (it carries a `/v1/ai/{proxy+}` route, auth NONE, proxied to `partner_api`). *Falls back to `LIMELI_AI_API_BASE`, then to `LIMELI_V1_BASE` (where `/v1/ai` 404s — a warning is printed). Set it to use the AI tools. |
| `LIMELI_ALLOW_DESTRUCTIVE` | no | `1` to also expose the gated `/v1/ai` `PUT` propose/triage writes (scope `write:ai` still required server-side) |

### TOKEN mode (Cognito id token)
| Env var | Required | Meaning |
|---------|----------|---------|
| `LIMELI_API_BASE` | yes | AdminApi invoke URL, e.g. `https://mkrirny1c2.execute-api.us-west-2.amazonaws.com/prod` |
| `LIMELI_AI_API_BASE` | no* | AiApi invoke URL for the `/tenant/ai/*` tools, e.g. `https://rim9ajjqmk.execute-api.us-west-2.amazonaws.com/prod`. *When unset those tools fall back to `LIMELI_API_BASE` and 404. |
| `LIMELI_BEARER_TOKEN` | yes | Cognito **id token** (the dashboard login token) |
| `LIMELI_TENANT_ID` | no | Super-admin only: target tenant (sent as `X-Tenant-Id`) |
| `LIMELI_ALLOW_DESTRUCTIVE` | no | `1` to also expose the gated write tools: DELETE, backfill, and the `/tenant/ai/*` propose/triage writes |

`LIMELI_OPENAPI_SPEC` (both modes, optional) overrides the OpenAPI spec path.

## Install (one command)

The package publishes a `limelijourney-mcp` console entry point. From a git checkout:

```bash
# Run straight from the repo with uv (no install), KEY mode:
LIMELI_API_KEY="llj_live_…" LIMELI_V1_BASE="https://<partner-id>.execute-api.us-west-2.amazonaws.com/prod" \
  uvx --from git+https://github.com/ReswanMahmood/limelijourney#subdirectory=mcp-server limelijourney-mcp

# Or install it (pip / pipx) and run the entry point:
pip install ./mcp-server            # or: pipx install ./mcp-server
LIMELI_API_KEY="llj_live_…" LIMELI_V1_BASE="https://…/prod" limelijourney-mcp
```

Runnable from a plain checkout too (no install): `python mcp-server/server.py`.
List the generated tools without running the server: `python -m limeli_mcp.tools` (from `mcp-server/`).

## Claude Desktop / Claude Code config

**KEY mode (recommended)** — one command, no local path:
```json
{
  "mcpServers": {
    "limelijourney": {
      "command": "uvx",
      "args": ["limelijourney-mcp"],
      "env": {
        "LIMELI_API_KEY": "llj_live_…",
        "LIMELI_V1_BASE": "https://<partner-id>.execute-api.us-west-2.amazonaws.com/prod",
        "LIMELI_V1_AI_BASE": "https://rim9ajjqmk.execute-api.us-west-2.amazonaws.com/prod"
      }
    }
  }
}
```
(Use `"args": ["--from", "git+https://github.com/ReswanMahmood/limelijourney#subdirectory=mcp-server", "limelijourney-mcp"]` to run straight from the repo before it's published.)

**TOKEN mode** — a loose checkout:
```json
{
  "mcpServers": {
    "limelijourney": {
      "command": "python",
      "args": ["/abs/path/to/mcp-server/server.py"],
      "env": {
        "LIMELI_API_BASE": "https://mkrirny1c2.execute-api.us-west-2.amazonaws.com/prod",
        "LIMELI_AI_API_BASE": "https://rim9ajjqmk.execute-api.us-west-2.amazonaws.com/prod",
        "LIMELI_BEARER_TOKEN": "<cognito-id-token>"
      }
    }
  }
}
```

## Layout
- `limeli_mcp/client.py` — HTTP client for both modes/gateways. TOKEN mode: `Authorization:
  Bearer <jwt>` (+ optional `X-Tenant-Id`), `/tenant/ai/*` → `ai_base_url`, rest → `base_url`.
  KEY mode: `X-Api-Key`, entity/report → `/v1/*` on `v1_base_url`, AI → `/v1/ai/<doc>` on
  `v1_ai_base_url`, unmapped paths → `ApiError(not_available_with_api_key)`. The mapping is
  `map_to_v1_path()`. Envelope unwrap + error-taxonomy → `ApiError`. stdlib only; injectable
  transport for tests.
- `limeli_mcp/tools.py` — spec-driven tool registry (re-exports the shared-layer generator;
  adds the MCP-local `/tenant/ai/*` write gate — reads open, writes behind the flag). Reads
  the spec from the repo `docs/openapi.yaml` or the copy bundled in the wheel.
- `limeli_mcp/cli.py` — the `limelijourney-mcp` runtime + MCP stdio adapter (needs the `mcp`
  SDK; the core does not). `server.py` is a thin launcher that calls it.
- `pyproject.toml` — packaging (console entry point + deps); bundles the spec + canonical
  generator into the wheel so an install outside the repo is self-contained.
- Isolation + KEY-mode test: [`local/test_mcp_server.py`](../local/test_mcp_server.py).
