Metadata-Version: 2.4
Name: drafter-mcp
Version: 0.1.0
Summary: MCP server that turns planning conversations into ordered feature plans
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: mcp>=1.2.0

# Planner API

HTTP wrapper over the personal-stack planner. Two modes: **subscription** (the host's
`claude` CLI thinks on a Claude subscription via `CLAUDE_CODE_OAUTH_TOKEN`, no API key)
or **BYOK** (the caller sends `X-Anthropic-Key`). A public deployment locks every
non-health endpoint behind a shared secret (`X-Planner-Token`).

## Endpoints
- `GET  /health` — liveness. Always open (Railway healthcheck).
- `POST /plan` — `{conversation, person}` → ordered, LLM-labeled, personalized plan
  with `analytics.planId`.
  Auth: `X-Planner-Token` if `PLANNER_ACCESS_TOKEN` is set; LLM via the subscription
  (subscription mode) or `X-Anthropic-Key` (BYOK).
- `POST /feedback` — `{person, plan_id, liked, comment?}` → records acceptance or
  dislike without changing weights.
- `POST /correct` — `{person, plan_id?, corrected_plan? | corrected_order?,
  proposed_order?}` → tunes the layer preference immediately and banks
  decomposition ground truth. No key (offline).
- `POST /analytics/event` — `{person, kind, payload}` → internal redirect events
  for `plan.delivered` and `invoker.redirect.disabled`.
- `GET  /analytics/{person}?limit=&kind=` — reads recent unified analytics events.
  Drop-off is `plan.delivered` with no later `feedback.*` for the same `planId`.
- `GET  /profile/{person}` — the person's current preference graph. No key.

## Run locally
```
pip install -r service/requirements-service.txt
uvicorn service.app:app --reload
curl localhost:8000/health
curl -X POST localhost:8000/plan -H 'X-Anthropic-Key: sk-ant-...' \
  -H 'content-type: application/json' \
  -d '{"conversation":"...","person":"edbert"}'
```

## Deploy (Railway, subscription mode, key-free)
The API runs on **Railway** at `api.invoker-control.dev` from `service/Dockerfile`,
which bakes in the `claude` CLI. The static landing page is a separate repo on
**Vercel** at `invoker-control.dev` (serverless is a poor fit for `/plan` — it makes
many sequential LLM calls and exceeds function time limits).

Railway auto-deploys on every push to main (GitHub integration). Set these env vars
in Railway (never commit them):
- `ANALYTICS_ENABLED=1` — mirrors local JSONL events to PostHog.
- `ANALYTICS_VENDOR=posthog` — the only outbound vendor currently supported.
- `POSTHOG_PROJECT_TOKEN` — PostHog project token (`phc_...`).
- `POSTHOG_HOST=https://us.i.posthog.com` — or your EU/self-hosted PostHog host.
- `CLAUDE_CODE_OAUTH_TOKEN` — from `claude setup-token`; lets the container's `claude`
  CLI think on your Claude subscription, so **no Anthropic API key is needed**.
- `PLANNER_SUBSCRIPTION_MODE=1` — `/plan` skips BYOK and uses that CLI.
- `PLANNER_ACCESS_TOKEN` — a shared secret; required as `X-Planner-Token` on every
  non-health endpoint so the public URL can't spend your subscription.

The MCP redirect (`integrations/invoker_planner`) defaults `PLANNER_URL` to
`https://api.invoker-control.dev` and forwards `X-Planner-Token` when
`PLANNER_ACCESS_TOKEN` is set in its env (see `install.py --access-token`).

BYOK still works: if `PLANNER_SUBSCRIPTION_MODE` is unset, `/plan` requires
`X-Anthropic-Key` and uses the caller's Anthropic API key instead.

## Scaling later (subscription → API key)
The container is the unit of scale; Railway can run N replicas and autoscale on load
(Railway → service → Settings → enable replicas/autoscaling). No code change.

The **subscription** path (`CLAUDE_CODE_OAUTH_TOKEN` + `PLANNER_SUBSCRIPTION_MODE`) is
for **one person / low volume** — a Max plan has rate limits and isn't meant to back a
multi-user service, so it won't scale to many concurrent users. To scale for real users:
1. In Railway, remove `PLANNER_SUBSCRIPTION_MODE` (and `CLAUDE_CODE_OAUTH_TOKEN`).
2. Either set a server-side `ANTHROPIC_API_KEY` (metered, you pay), or have each caller
   send their own `X-Anthropic-Key` (BYOK — `complete_json` already prefers the
   request-scoped key). Keep `PLANNER_ACCESS_TOKEN` as the endpoint lock either way.
3. Then turn up Railway autoscaling; each request is metered per token, so it scales cleanly.

## How the key flows (security)
`X-Anthropic-Key` → set on a request-scoped `contextvar` in `_byok` → read by
`registry/.../llm.py::complete_json` for that request's Anthropic calls → reset in
a `finally`. It is excluded from the disk cache (cache key = model+prompt only)
and never logged.

## Not yet (next milestones)
- **State → managed Postgres + real auth.** Profiles/learned/corrections are still
  file-based here, keyed by `person`. For multi-user production this must move to a
  per-user database (also where the moat lives).
- Rate limiting / quotas, accounts, the frontend.
