Metadata-Version: 2.4
Name: invincible-ai
Version: 0.4.0
Summary: Multi-provider AI gateway with session memory and MCP tool execution
Author-email: "Islam MD. Jahidul" <jahidulislam199923@gmail.com>
License-Expression: MIT
Keywords: ai,gateway,llm,mcp,openai,anthropic,proxy,failover,byok,agents
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: fastapi
Requires-Dist: uvicorn
Requires-Dist: httpx
Requires-Dist: PyYAML
Requires-Dist: python-dotenv
Requires-Dist: click
Requires-Dist: sqlalchemy[asyncio]>=2
Requires-Dist: asyncpg
Requires-Dist: greenlet
Requires-Dist: alembic>=1.13
Requires-Dist: argon2-cffi
Requires-Dist: cryptography
Requires-Dist: jinja2
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-asyncio; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Requires-Dist: setuptools>=77; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Dynamic: license-file

# Invincible — AI Continuity Gateway & Local MCP Agent

> The Python package is named `invincible` (the repository directory is
> `ai-gateway`). Throughout this repo the project is referred to as
> **Invincible**.

> **Project direction:** Invincible is a **remote-first, multi-user AI
> continuity platform** — accounts, projects, shared memory across models,
> context intelligence, and a web dashboard (invincible-ai.me). Users
> install one CLI command on their own PC and pair with the hosted service;
> the database, provider credentials, and all state live server-side. The
> same code runs as a single-user self-host. See
> [docs/ROADMAP.md](docs/ROADMAP.md) for the direction, what is implemented
> today, and what is planned.

---

## What is Invincible?

A **remote-first, multi-user AI gateway**: one Python (FastAPI) service that
many users sign into, each with their own projects, API keys, provider
credentials, memory, and continuity — served over HTTPS from any host. The
same code runs as a single-user self-host on a laptop (the local/self-hosted
mode is supported indefinitely); nothing else changes between the two.

It serves three roles in one process:

1. **Self-Service BYOK Gateway** — an OpenAI-compatible `/v1/chat/completions`
   endpoint where every user authenticates with their own `inv_` API key and
   routes through **their own connected provider credentials** (BYOK), with
   transparent failover on rate limits (429) and server errors, so a
   free-tier 429 no longer kills an agent's workflow. It also speaks the
   **Anthropic Messages API** (`POST /v1/messages`) and the **OpenAI
   Responses API** (`POST /v1/responses`), so Claude Code, Codex CLI, and
   other native clients plug in with a one-line config change.
2. **Multi-user account platform** — browser accounts (email + password, or
   GitHub), a dashboard for sessions, memory, usage, and provider
   credentials, and strict per-user ownership: one account can never read
   another's sessions, memory, or history.
3. **MCP Tool Server** — a JSON-RPC 2.0 `/mcp` endpoint exposing
   `read_file`, `execute_bash`, and `write_file` to a cloud-hosted AI. With
   `INVINCIBLE_AGENT_ROUTING=1` (required on any public multi-user
   deployment) the tools execute on **the user's own paired machine**, so a
   remote AI acts on *their* files under *their* account.

### Why it exists

- **The 429 problem.** AI coding agents using free/open-source providers
  get killed when they hit a rate limit. Invincible
  sits between the agent and the providers; on a 429 (or 5xx) it records the
  failure, puts the provider in a short cooldown, and retries the next
  credential in the user's own routing order. The agent sees a single,
  stable endpoint.
- **The cloud-to-local gap.** Cloud AI tools (e.g. the Claude web/mobile app)
  can reason well but cannot read the user's files, write to disk, or run
  terminal commands. Invincible's MCP server exposes those capabilities over
  HTTPS, so a remote model can act on each user's own machine — under that
  user's confirmation for anything destructive.

---

## Features

| Feature | What it gives you |
|---|---|
| **Remote-first, multi-user** | One deployment serves many accounts over HTTPS: browser accounts (email+password or GitHub), per-user projects/API keys/provider credentials, a dashboard, and strict per-user ownership — one account can never read another's data (audited; see [docs/MULTI-TENANT-AUDIT.md](docs/MULTI-TENANT-AUDIT.md)). The same core self-hosts on a laptop, and [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) covers running it on a host. |
| **BYOK routing** | Every user connects their own provider credentials (dashboard → Providers) and routes only through them: `auto`/`pinned`/`chain` routing per user, 429/5xx → cooldown + next credential, 401/403 → skip, network errors → next. No credentials connected → HTTP 400. All credentials exhausted → HTTP 503. |
| **Exponential cooldown** | 30s → 60s → 120s → 240s → capped at 300s; a success resets the counter (in-memory, process-scoped). |
| **Conversation memory** | PostgreSQL-backed (Phase 16), keyed by the `X-Session-Id` header (default `default`). History is merged into every request and the assistant reply is persisted back. |
| **Context trimming** | Per-credential `max_context`; system messages always kept; everything else dropped as atomic *turns* (an assistant `tool_calls` is never separated from its tool results); the most recent turn is always sent. |
| **Per-provider timeouts** | Split connect/read/write/pool with sane defaults and per-provider overrides. |
| **MCP tool server** | `read_file` (no approval), `execute_bash` and `write_file` (staged, then approved via a token round-trip through the `confirm_action` tool), guarded by denylists and an **OAuth 2.1 + PKCE bearer-token** auth layer (account login + per-client consent, tokens don't survive on requests like a shared header does). With `INVINCIBLE_AGENT_ROUTING` on, confirmed actions execute on the **user's own PC** via the paired `invincible agent` (server keeps every security decision; see [§ Run tools on your own PC](#run-tools-on-your-own-pc-the-local-agent)). |
| **Accounts & projects** (Phase 3) | Sign up / sign in in a browser (email + argon2id passwords, or **GitHub login**), manage your own projects and `inv_` API keys over HTTP, list your sessions — all under ownership predicates so users never see each other's data. Pair a CLI with `invincible login` via device-code approval. |
| **Protocol-agnostic** | Native **OpenAI**, **Anthropic**, and **Responses** protocols, all translated into one internal message model. Claude Code works with `ANTHROPIC_BASE_URL` pointing at the gateway; Codex CLI works out of the box. |

---

## Installation

**Users: one install command. That's all.**

```bash
pip install invincible-ai
invincible agent
```

The first run pairs your machine with the hosted service
(`https://invincible-ai.me` — the default): your browser opens, you
register or sign in, click **Approve**, and the agent starts. There is
**no database to set up, no `.env`, no provider configuration** on your
machine — your account, connected provider keys, memory, and sessions all
live on the hosted service. The local agent only executes confirmed tool
actions on your PC. Finish by adding `https://invincible-ai.me/mcp` as the
MCP connector in Claude (or any MCP client) — see
[Quick Start](#quick-start). (`invincible login` does the same pairing
without starting the agent loop.)

**Operators only: running your own server.** Everything above works
against the hosted service without it. If you self-host or operate an
instance, you need Python 3.10+ **and** a PostgreSQL database (all state —
conversations, OAuth grants, task state, staged approvals — lives there;
`INVINCIBLE_DB_URL` is required to start):

```bash
pip install -e .
invincible setup --db-url postgresql://user:pass@your-db-host:5432/invincible
invincible db upgrade                # create/migrate the schema (explicit, never auto-run)
invincible start --host 0.0.0.0      # omit --host for a loopback-only dev server
```

TLS, `$PORT`, proxy headers, the two-role database split, the container
start command, required secrets, and the go-live checklist:
[docs/DEPLOYMENT.md](docs/DEPLOYMENT.md). Development shortcuts
(`invincible dev-db`, `docker compose up`) and the full CLI reference:
[docs/CONFIGURATION.md](docs/CONFIGURATION.md).

---

## Quick Start

### A. Use the hosted service (one install command)

```bash
pip install invincible-ai
invincible agent       # first run: browser opens → sign in or register → Approve
```

Pairing registers your account on `https://invincible-ai.me` and saves the
minted key to `~/.invincible/config.json`. Then, in the dashboard:

1. **Providers** — connect your own provider keys (BYOK).
2. **MCP connector** — add `https://invincible-ai.me/mcp` to Claude (or any
   MCP client) and approve the client when the browser asks.

With the agent running, a remote AI can read/write files and run commands
**on your machine**, under your account's approvals. Prefer raw API access
instead? Mint an `inv_` key (dashboard → Account → API keys) and call the
gateway directly:

```bash
export INVINCIBLE_BASE=https://invincible-ai.me
export INVINCIBLE_API_KEY=inv_...        # dashboard -> Account -> API keys

curl $INVINCIBLE_BASE/v1/chat/completions \
  -H "Authorization: Bearer $INVINCIBLE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"messages": [{"role": "user", "content": "Hello!"}]}'
```

Every account is independent: the same commands work for every user with
their own `inv_` key, their own connected provider credentials, and their own
sessions — no account can see or route through another's.

### B. Run your own server (operators / self-hosters — users never do this)

```bash
invincible setup --db-url postgresql://user:pass@db-host:5432/invincible
invincible db upgrade               # create/migrate the schema (explicit, never auto-run)
invincible start                    # http://127.0.0.1:8000 (dev loopback)
invincible start --host 0.0.0.0     # reachable from other machines
```

`invincible setup` writes missing secret values (`INVINCIBLE_OWNER_SECRET`)
as random `secrets.token_urlsafe(32)` tokens, generates the BYOK credential
master key (`INVINCIBLE_CREDENTIAL_KEY`), and takes the `INVINCIBLE_DB_URL`
via `--db-url` (non-interactive) — preserving your existing `.env` comments
and values. `INVINCIBLE_OWNER_SECRET` signs account browser sessions
(dashboard login, OAuth consent) — not something `/mcp` requests send.

For a real host — TLS, `$PORT`, proxy headers, the two-role database split,
the container start command (`0.0.0.0:$PORT`), and the go-live checklist —
follow [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md). Development-only database
shortcuts are covered in [docs/CONFIGURATION.md](docs/CONFIGURATION.md).

Clients of your own server point at it explicitly:
`invincible agent --server https://mycompany.ai` (and
`ANTHROPIC_BASE_URL=https://mycompany.ai` for Claude Code). The hosted
service is the CLI default, so your users pass no flag at all.

See [Examples](#examples) for ready-to-run `curl` calls, or continue reading
for the full configuration, API, and tooling reference.

---

## Configuration

Everything is environment variables plus one YAML file — no other config.

### `.env` variables

| Variable | Required by | Purpose |
|---|---|---|
| `INVINCIBLE_DB_URL` | startup (**required since Phase 16**) | PostgreSQL DSN for all persistent state. Use a **managed/reachable** PostgreSQL on a remote deployment, e.g. `postgresql+asyncpg://invincible_app:***@your-db-host:5432/invincible` (Neon, RDS, Azure Database for PostgreSQL, a container, or your own cluster). `invincible dev-db` (local, dev-only) or the bundled compose pair are the laptop shortcuts. Masked in `doctor` output. |
| `INVINCIBLE_MIGRATE_DB_URL` | container/platform deploys | Schema-owner DSN used **only** for the one `db upgrade` the image runs at startup (falling back to `INVINCIBLE_DB_URL` when unset), so migrations run as the migrate role while the server serves as the CRUD-only runtime role. See [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) §Database. |
| `INVINCIBLE_OWNER_SECRET` | account sessions | Signs account browser sessions (dashboard login, OAuth consent). **Not** sent on `/mcp` — requests use short-lived OAuth Bearer tokens. **If unset, browser sessions fail closed (no login, no consent).** The legacy `MCP_SHARED_SECRET` key is still read as a fallback. Rotating it logs every browser out but does **not** revoke MCP grants — use `invincible oauth revoke <client_id>` for that. |
| `INVINCIBLE_CREDENTIAL_KEY` | BYOK | Fernet master key encrypting stored provider credentials at rest. Generated by `invincible setup` (or `invincible secret credential-key`); **back it up** — losing it makes every saved provider key undecryptable. Never rotated by `setup --force`. |
| `INVINCIBLE_PERSIST_PENDING_ACTIONS` | startup | **Opt-in**: when set, staged `execute_bash`/`write_file` approvals are written to the PostgreSQL database (`pending_actions` table) and survive a server restart. **Off by default** — pending actions are memory-only and a restart orphans them (clean slate). |
| `INVINCIBLE_AGENT_ROUTING` | startup (**required on public multi-user deployments**) | Routes confirmed tool execution to the caller's paired agent (`invincible agent`) instead of the server host. Unset/off is the single-user self-host posture (a one-person `invincible start`), where tools run on the server host under the server's privileges — **never acceptable when strangers can register**. Set `1` on every public/hosted deployment: see [docs/SECURITY.md](docs/SECURITY.md) §10 and the checklist in [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md). |
| `INVINCIBLE_AGENT_ROOT` | agent | Sandbox root for the local agent (default: the user's home directory). Reads and writes outside it are blocked; `.env*`, `.git`, `.ssh`, SSH keys, `*.pem`, `*credentials*` are blocked by name everywhere. |

`/v1/*` requests authenticate with **per-user `inv_` API keys** (minted on
the dashboard's Account page, or by the host with
`invincible api-key create --user <email-or-id>`). Each user connects their
own provider credentials on the dashboard's Providers page (BYOK) and
routes only through them — there is no shared gateway key and no shared
provider pool.

The secrets are **independent**: a leaked `/mcp` URL alone is not enough to
reach tool execution (a live OAuth bearer token is required as well), and
rotating one secret never affects the other.

### `providers.yaml`

The packaged `invincible/providers.yaml` is a static fixture (tests and
direct `Router` construction); live traffic never reads it — every
`/v1/*` request routes through the caller's own connected BYOK
credentials. There is no repository-root copy.

Full reference — schema, validation rules, timeout resolution:
[docs/CONFIGURATION.md](docs/CONFIGURATION.md).
How to add a provider, aliases, and supported shapes:
[docs/PROVIDERS.md](docs/PROVIDERS.md).

---

## CLI Commands

Two commands, both exposed as `invincible` and `inv`:

| Command | Purpose |
|---|---|
| `invincible setup` | Create/update `.env`: generates missing secrets (`token_urlsafe(32)`, never echoed), prompts for provider keys, preserves existing comments/values; carries a legacy `MCP_SHARED_SECRET` over to `INVINCIBLE_OWNER_SECRET` automatically. `--force` re-prompts existing values. |
| `invincible secret rotate` | Generate a brand-new `INVINCIBLE_OWNER_SECRET` and rewrite it in place — no manual `.env` editing, never echoes the value (unless `--show`). Preserves every other line; migrates a legacy `MCP_SHARED_SECRET` key away. Does **not** revoke already-issued OAuth grants (that's `invincible oauth revoke`). |
| `invincible start` | Start the server. `--host` (default `127.0.0.1`; pass `0.0.0.0` to be reachable from other machines), `--port` (default `8000`), `--reload`, `--log-level`, `--env-file`, `--config` (custom providers.yaml), `--tunnel/--no-tunnel` (local convenience: starts a Cloudflare tunnel named `invincible` by default so a laptop can be reached from the internet), `--tunnel-name` (or `INVINCIBLE_TUNNEL_NAME`). The tunnel is shut down with the server (Ctrl+C or a crash); a dead tunnel is reported as soon as it exits. There is no database flag — `INVINCIBLE_DB_URL` comes from the env/.env. **Hosted platforms do not use this command**: the container command in `Dockerfile`/`railway.json`/`Procfile` binds `0.0.0.0:$PORT` with proxy headers — see [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md). |
| `invincible doctor` | Environment/config diagnostics: providers.yaml, secrets, and PostgreSQL connectivity + schema revision (DSN always password-masked); loud FAIL on a stale/unmanaged schema. |
| `invincible dev-db` | Provision or verify a local Postgres **development** database (Docker fallback included) and print/write a working `INVINCIBLE_DB_URL`. Loopback-only and dev-credential by design — never the provisioning path for a remote/hosted database ([docs/DEPLOYMENT.md](docs/DEPLOYMENT.md)). |
| `invincible db upgrade` | Run the packaged Alembic migrations to head against `INVINCIBLE_DB_URL`. Explicit by design — nothing auto-migrates. |
| `invincible oauth list` | Show registered OAuth clients, their redirect URIs, and active/revoked grants. |
| `invincible oauth revoke <client_id>` | Revoke every access/refresh token for a client immediately. |
| `invincible oauth test-client` | Headless helper: registers a client, approves it, and prints a ready-to-use Bearer token + curl for `/mcp` (no browser needed). |
| `invincible api-key create --user <id-or-email> --label L` | Mint an API key (`inv_…`) under a named account (host tool) — raw key shown **once**, only its SHA-256 hash is stored. |
| `invincible api-key list` | List API keys by visible prefix (never hashes or raw keys). |
| `invincible api-key revoke <id-or-prefix>` | Revoke a key immediately. |
| `invincible users list` | List accounts (host tool; roles are informational only). |
| `invincible users reset-password <email>` | Reset an account's password (host recovery path — database access is the proof of authority; `--generate` prints a strong password once). Every browser session for the account is signed out; `inv_` keys and MCP tokens are untouched. |
| `invincible login [--server URL]` | Pair this machine with an Invincible server (device flow): opens the approval page in your browser — click Approve and the command finishes, saving the `inv_` key to `~/.invincible/config.json`. Defaults to the hosted service (`https://invincible-ai.me`); pass `--server` for a self-hosted or local server. URL + code are printed for headless terminals; the Account page also has a "Pair a device" box for typing a code by hand. |
| `invincible agent` | Run the local agent (Phase 10): polls the paired server for confirmed tool jobs and executes them on **this machine** with your own user privileges — denylist re-checked locally, reads/writes sandboxed to your home. Ctrl+C to stop. First run pairs automatically (device flow); `invincible login` is the explicit pairing/repair tool. |

```bash
invincible setup --force
invincible secret rotate            # new owner secret, in place
invincible secret rotate --show     # ...and print it (rarely needed)
invincible start --port 9000 --config ./my-providers.yaml
```

Full CLI reference: [docs/CONFIGURATION.md](docs/CONFIGURATION.md) → *CLI reference*.

---

## API

### Endpoints

| Method | Path | Auth | Purpose |
|---|---|---|---|
| `GET` | `/` | none | Health check → `{"status": "healthy"}` |
| `HEAD` | `/` | none | 200 OK — Claude Code's base-URL probe |
| `GET` | `/health` | none | Service detail → `{"service", "status", "version"}` |
| `GET` | `/v1/models` | `Authorization: Bearer inv_…` | OpenAI-compatible model list built from **your** connected credentials (empty when you have none) |
| `POST` | `/v1/chat/completions` | `Bearer inv_…` API key | OpenAI chat completion routed through your own credentials with failover |
| `POST` | `/v1/messages` | `Bearer inv_…` API key | Anthropic Messages API (same routing) |
| `POST` | `/v1/responses` | `Bearer inv_…` API key | OpenAI Responses API for Codex CLI (same routing) |

### Chat request

- **Body**: `{"messages": [...], "stream": false}` — OpenAI message format.
  `messages`, `stream`, and `model` are accepted. `model` is a soft routing
  hint (matches a configured alias or exact `model_id`; unknown names are
  ignored). Other OpenAI fields are rejected with **422**.
- **Streaming**: `stream: true` returns an OpenAI-compatible
  Server-Sent Events (`text/event-stream`) response. Each event is a
  `chat.completion.chunk` (`data: {…}\n\n`), and the stream ends with
  `data: [DONE]`. Chunks are forwarded from the upstream provider as they
  arrive — nothing is buffered. Providers that fail before the first chunk
  trigger the normal failover; an error after streaming has begun terminates
  the stream with a well-formed `data: {"error": …}` event.
- **Sessions**: history is loaded from PostgreSQL keyed by the `X-Session-Id`
  header (default `default`), prepended to your messages, and the assistant
  reply is persisted back. `session_id` is a partition key, not a credential.
  For streamed responses the reply is reconstructed from the chunk deltas and
  saved once the stream completes.
- **Response**: the upstream provider's JSON is forwarded **verbatim**
  (non-streaming).

### Anthropic Messages (`POST /v1/messages`)

Invincible also speaks the **Anthropic Messages API**, so Claude Code and
other Anthropic-native clients work without modification:

```bash
# .env for Claude Code (or your shell):
ANTHROPIC_BASE_URL=http://127.0.0.1:8000
```

Claude Code probes `HEAD /`, then calls `POST /v1/messages?beta=true` — both
are served. Supported request fields: `model`, `system`, `messages`,
`max_tokens`, `stream`. Everything else Claude Code sends (`tools`,
`tool_choice`, `metadata`, `temperature`, `top_p`, `top_k`,
`stop_sequences`, unknown fields, `anthropic-beta` / `anthropic-version`
headers, the `?beta=true` query) is **accepted and ignored** — never a 422.

The `model` field is treated as a **client hint**: if it matches one of
**your** connected credentials (an alias or an exact `model_id`) that
credential is *preferred* — the Router still fails over through the rest of
your routing order if it is down. An unknown model name (like Claude Code's
own model ids) changes nothing. The upstream model always comes from your
connected credentials, and the response reports **that** model — the one
that actually served — so a fallback across models stays visible to the
client instead of being masked by an echo of what you asked for.

- **Streaming**: `stream: true` returns Anthropic SSE events in the
  canonical order — `message_start` → `content_block_start` →
  `content_block_delta` (one per text delta) → `content_block_stop` →
  `message_delta` → `message_stop`. `tool_use` content blocks are preserved
  and streamed as structured events (`content_block_start` +
  `input_json_delta` frames) with `stop_reason: "tool_use"` at the end;
  `tool_result` blocks in the request are carried as `role: "tool"`
  messages, so tool-shaped conversations round-trip losslessly (ids
  preserved). `image` content blocks are still skipped. A mid-stream
  upstream failure emits a well-formed Anthropic `error` event and closes —
  never malformed SSE.
- **Sessions**: the same `X-Session-Id` header and PostgreSQL store are used,
  and history is serialized in the shared internal format — an OpenAI
  client and a Claude Code session on the same id see the same
  conversation.
- **Errors**: mapped to Anthropic error types (`invalid_request_error`,
  `authentication_error`, `permission_error`, `not_found_error`,
  `rate_limit_error`, `api_error`, `overloaded_error`) with sanitized
  messages; upstream provider bodies are never forwarded.
- **Unsupported today**: image content (skipped during conversion).

### Status codes

| Status | When |
|---|---|
| `200` | Upstream success — JSON body forwarded verbatim, or SSE stream (`stream: true`) |
| `400` | Caller has no connected provider credentials (BYOK) — connect one on the dashboard's Providers page |
| `401` | Missing/invalid `inv_` API key |
| `422` | Body fails validation (missing `messages`, extra fields) |
| `4xx` | Upstream returned a non-failover error (e.g. 400) — forwarded verbatim |
| `503` | All of the caller's credentials failed or are in cooldown (before streaming starts) |

The Anthropic endpoint uses the same statuses; error bodies are Anthropic
shaped (`{"type": "error", "error": {"type": …, "message": …}}`) and map to
Anthropic error types.

Full contract — sessions, trimming, timeout semantics:
[docs/API_REFERENCE.md](docs/API_REFERENCE.md).

---

## MCP Support

`POST /mcp` implements a minimal JSON-RPC 2.0 subset: `initialize`,
`tools/list`, and `tools/call`. Protocol version: `2025-06-18`.

- **Auth**: OAuth 2.1 + PKCE via the built-in authorization server. Clients
  discover it at `/.well-known/oauth-protected-resource` (RFC 9728), register
  at `/oauth/register`, get owner approval on the `/oauth/authorize` consent
  page, then send `Authorization: Bearer <access_token>` on every `/mcp`
  request. Wrong/missing/expired/revoked token → `401` with a
  `WWW-Authenticate: Bearer resource_metadata="…"` challenge. (No
  `X-MCP-Secret` header anymore; the legacy `MCP_SHARED_SECRET` env var is
  only read as a fallback for the owner login.)
- **Notifications**: a request without an `id` still runs its side effect
  but the server replies `204 No Content` with no body.

### Tools

| Tool | Arguments | Confirmation | Gate |
|---|---|---|---|
| `read_file` | `path` | **No** | Blocks only real secrets/state: `.env*`, `sessions.db`, `.git/`. **Allows** `invincible/`, `tests/`, `providers.yaml`. |
| `execute_bash` | `command` + a `confirm_action` token round-trip | **Yes** — staged with a token; runs only after `confirm_action(token, approve=true)` (30s execution timeout) | Blocks high-blast-radius commands (`rm -rf /`, fork bombs, `dd of=/dev/`, `mkfs`, `sudo`, `curl \| sh`, `rd /s C:\`, …). |
| `write_file` | `path`, `content` + a `confirm_action` token round-trip | **Yes** — staged with a token; writes only after `confirm_action(token, approve=true)` | Blocks writes to `.env*`, `providers.yaml`, `sessions.db`, `invincible/`, `tests/`, `.git/`. Creates parent directories. |
| `confirm_action` | `token`, `approve` | — | Approves/denies a pending `execute_bash`/`write_file`; token is single-use and expires after 10 minutes. |

Security model, full denylist inventory, and known limits:
[docs/SECURITY.md](docs/SECURITY.md).

### Run tools on your own PC (the local agent)

On a hosted deployment with `INVINCIBLE_AGENT_ROUTING=1`, confirmed tool
actions execute on **your machine**, not the server — the security
checks (denylist, staging, approval tokens) all stay server-side, but
the work travels to a paired local agent. From zero to "my AI just ran a
command on my PC" is two commands — no account to create first, no
separate pairing step:

```bash
pip install invincible-ai
invincible agent       # first run: browser opens → sign in or register → Approve
```

On first run the agent pairs the machine itself: the browser opens on
the approval page (create your account right there if you don't have
one, then click **Approve**), the minted key is saved to
`~/.invincible/config.json`, and the agent falls straight into its
polling loop — it never asks you to run another command first. Every
later start is just `invincible agent` again; pairing happens once per
machine, ever.

Self-hosters point at their own server once:
`invincible agent --server https://mycompany.ai` (or
`--server http://127.0.0.1:8000` against a local `invincible start`).
`invincible login` remains as the explicit re-pair/repair tool.

Then connect Claude/Grok/any MCP client to `https://your-server.example.com/mcp`
as usual. While the agent is running, its console shows each dispatched
job; the dashboard MCP page shows a live **Agent: online/offline**
badge. The agent sandbox is your home directory (`INVINCIBLE_AGENT_ROOT`
to override), with `.env*`, `.git`, `.ssh`, and key material blocked
everywhere. An offline agent means tool calls answer `agent_offline`
immediately — nothing hangs. Full transport and threat model:
[docs/MCP_PROTOCOL.md](docs/MCP_PROTOCOL.md) and
[SECURITY.md §10](docs/SECURITY.md).

---

## Provider Routing

Every `/v1/*` request routes through **the caller's own connected BYOK
credentials** (dashboard → Providers), in the order the user configured
(`auto` by ability, or an explicit `pinned`/`chain` order). Per attempt:

| Upstream status | Router behavior |
|---|---|
| `200` | `record_success` (resets cooldown) → return body |
| `429` / `5xx` | `record_failure` → cooldown → **try next credential** |
| `401` / `403` | Skip that credential for this request → **try next** |
| Other `4xx` (e.g. `400`) | **Abort** — forward the provider's status and body |
| Network error | `record_failure` → cooldown → **try next credential** |
| In cooldown | Skipped silently (log only) |

All of the caller's credentials exhausted → **HTTP 503**. No credentials
connected at all → **HTTP 400**. Cooldowns follow
`30 * 2**(failures-1)`, capped at **300s**; all health state is in-memory and
resets on restart.

Deep dive (failover state machine, context trimming): [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).

---

## Examples

Every call below uses `$INVINCIBLE_BASE` — the hosted service, or your own
server:

```bash
export INVINCIBLE_BASE=https://invincible-ai.me   # or http://127.0.0.1:8000 locally
export INVINCIBLE_API_KEY=inv_...                 # dashboard -> Account -> API keys
```

### 1. Health check

```bash
curl $INVINCIBLE_BASE/
# {"status": "healthy"}
```

### 2. List models

```bash
curl $INVINCIBLE_BASE/v1/models \
  -H "Authorization: Bearer $INVINCIBLE_API_KEY"
# {
#   "object": "list",
#   "data": [
#     {"id": "<your connected credentials' model ids>", "object": "model", "owned_by": "invincible"},
#     {"id": "<your configured aliases>", "object": "model", "owned_by": "invincible"}
#   ]
# }
```

The list is built from **your** connected BYOK credentials, so it reflects
exactly what the gateway can route to for you (an empty list when you have
none connected). Real model ids are listed first, then the aliases you
configured.

### 3. Chat with session memory

```bash
curl $INVINCIBLE_BASE/v1/chat/completions \
  -H "Authorization: Bearer $INVINCIBLE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Session-Id: my-conversation" \
  -d '{"messages": [{"role": "user", "content": "Hello!"}]}'
```

The assistant reply is stored under `my-conversation` and will be included in
your next request with the same `X-Session-Id` — the model remembers the
conversation.

### 4. Stream a chat (SSE)

```bash
curl -N $INVINCIBLE_BASE/v1/chat/completions \
  -H "Authorization: Bearer $INVINCIBLE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"messages": [{"role": "user", "content": "Hello!"}], "stream": true}'
```

`-N` (aka `--no-buffer`) prints each event as it arrives. Tokens are streamed
as OpenAI-compatible `chat.completion.chunk` events:

```json
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1783161600,"model":"gemini-2.5-flash","choices":[{"index":0,"delta":{"content":"Hello"},"finish_reason":null}]}

data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1783161600,"model":"gemini-2.5-flash","choices":[{"index":0,"delta":{"content":"!"},"finish_reason":null}]}

data: {"id":"chatcmpl-...","object":"chat.completion.chunk","created":1783161600,"model":"gemini-2.5-flash","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: [DONE]
```

### 5. Use it from Claude Code (Anthropic)

```bash
ANTHROPIC_BASE_URL=https://invincible-ai.me claude   # or your own server URL
```

Claude Code probes `HEAD /`, then calls `POST /v1/messages` with streaming.
You can send the same call directly:

```bash
curl $INVINCIBLE_BASE/v1/messages \
  -H "Authorization: Bearer $INVINCIBLE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"claude-sonnet-4","max_tokens":1024,
       "messages":[{"role":"user","content":"Hello!"}]}'
```

And stream it (`stream: true`) to receive Anthropic SSE events ending in
`message_stop`.

### 6. List MCP tools

First get an MCP access token. On the hosted service, add the connector in
your MCP client at `$INVINCIBLE_BASE/mcp` and approve it in the browser —
the client then holds the token. On a **local/self-hosted** server the
headless helper registers a client against *that* server's database and
prints a ready-to-use Bearer token:

```bash
invincible oauth test-client --env-file .env   # prints a token + curl example
export ACCESS_TOKEN=...
```

```bash
curl -X POST $INVINCIBLE_BASE/mcp \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

### 7. Run a command via MCP

```bash
curl -X POST $INVINCIBLE_BASE/mcp \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call",
       "params":{"name":"execute_bash","arguments":{"command":"git status"}}}'
```

The call returns a `pending_confirmation` result carrying a token. To
approve it, call `confirm_action` with that token (or deny with
`approve: false`):

```bash
curl -X POST $INVINCIBLE_BASE/mcp \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call",
       "params":{"name":"confirm_action",
                 "arguments":{"token":"<token from step 2>","approve":true}}}'
```

The command runs (or the file is written) only after approval; the token is
single-use and expires after 10 minutes.

### 8. Reach it from a cloud AI

On a deployed server the plain HTTPS URL is the entry point: point the MCP
client at `https://your-domain/mcp` (or `https://invincible-ai.me/mcp` on the
hosted service) and approve the connector in the browser.

A laptop has no public URL, so `invincible start` starts a Cloudflare tunnel
alongside the server by default: `cloudflared tunnel run <name>` (name
defaults to `invincible`, override with `--tunnel-name` or
`INVINCIBLE_TUNNEL_NAME`). cloudflared's log lines (and the public URL when
cloudflared prints one) appear prefixed `[tunnel]`; a tunnel that dies is
reported as soon as it exits, and it is shut down with the server. To skip
the tunnel, pass `--no-tunnel`.

For a one-off quick tunnel instead (no named-tunnel config required):

```bash
cloudflared tunnel --url http://127.0.0.1:8000
# → https://random-name.trycloudflare.com  — call /mcp on this URL
```

Tunnel or not, the URL alone is useless without an access token — and any
valid token can be revoked immediately with `invincible oauth revoke <client_id>`.

More MCP protocol details: [docs/MCP_PROTOCOL.md](docs/MCP_PROTOCOL.md).

---

## Architecture

```
                         ┌──────────────────────────────────┐
  OpenAI-compatible      │  invincible/main.py              │
  agent  ─── /v1/chat ─► │  (FastAPI)                        │
         Claude Code     │                        compat/    │
  (Anthropic) ─ /v1/msg ►│  openai_compat ──────► anthropic │
                         │  │ mcp routers │                 │
  Cloud AI      ─── /mcp ─►  │ core/router.py               │
  (via tunnel)           │  │ core/tool_executor (denylist) │
                         └──────┬──────────────┬────────────┘
                                │              │
                  ┌─────────────▼──┐   ┌───────▼────────────┐
                  │ core/router.py │   │ core/tool_executor │
                  │ tiered failover│   │ (denylist + approval)│
                  │ + ctx trimming │   └─────────────────────┘
                  └───────┬────────┘
                          │
            ┌──────────────────────────┐
            │ core/provider_health.py  │
            │ core/session_store.py    │
            │ (PostgreSQL stores)      │
            └──────────────────────────┘
```

The compatibility layers (OpenAI and Anthropic) only translate; both
produce the same internal message model, which is what the Router, session
store, and trimming logic consume.

### Package layout

| Path | Role |
|---|---|
| `invincible/main.py` | FastAPI app, lifespan, auth dependencies, router wiring, `HEAD /` and `/health`. |
| `invincible/endpoints/openai_compat.py` | `POST /v1/chat/completions` (JSON + SSE streaming, session merge + upstream call); `GET /v1/models`. |
| `invincible/endpoints/anthropic_compat.py` | `POST /v1/messages`; translates Anthropic ↔ internal model, calls the same Router. |
| `invincible/models/anthropic.py` | Pydantic request model: only real fields declared; everything else ignored. |
| `invincible/compat/common.py` | Protocol-neutral internal-message/usage helpers shared by compat layers. |
| `invincible/compat/anthropic.py` | Pure Anthropic translators: flattening, finish-reason map, error map, Anthropic SSE streaming. |
| `invincible/endpoints/mcp.py` | `POST /mcp`; JSON-RPC 2.0 dispatch, `tools/list`, `tools/call`. |
| `invincible/core/router.py` | Tiered failover over per-user BYOK credentials, response trimming, timeouts. |
| `invincible/core/provider_health.py` | Per-credential failure counts + exponential cooldowns. |
| `invincible/core/db.py` | SQLAlchemy engine factory + schema metadata (single source of truth). |
| `invincible/migrations/` | Packaged Alembic environment (`invincible db upgrade`). |
| `invincible/core/session_store.py` | Conversation memory on PostgreSQL, partitioned by session id. |
| `invincible/core/tool_executor.py` | Denylists, pending-action approval (`confirm_action`), tool execution. |
| `invincible/endpoints/byok.py` | Per-user provider-credential management (connect, test, order, routing config). |
| `invincible/cli.py` | Click CLI: `setup`, `start`, `doctor`, `api-key`, `users`, `oauth`, `db`, `login`, `agent`. |
| `invincible/providers.yaml` | Static provider fixture (tests/direct construction; live traffic is BYOK-only). |

---

## Documentation

| Doc | What it covers |
|---|---|
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | Module map, request flows, context-trimming deep dive, failover state machine. |
| [docs/API_REFERENCE.md](docs/API_REFERENCE.md) | The `/v1/chat/completions` contract: request, response, status codes, failover semantics. |
| [docs/CONFIGURATION.md](docs/CONFIGURATION.md) | `.env` variables, `providers.yaml` schema, timeouts, database options, CLI reference. |
| [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) | Running it remotely: required env vars, ports/TLS/proxy headers, two-role database, migrations, single-instance constraints, go-live checklist. |
| [docs/PROVIDERS.md](docs/PROVIDERS.md) | Adding providers, the full schema, model aliases, auth types, supported shapes, troubleshooting. |
| [docs/MCP_PROTOCOL.md](docs/MCP_PROTOCOL.md) | Client-facing `/mcp` spec: JSON-RPC shape, tools, notifications, hosted URL vs. self-host tunnel. |
| [docs/SECURITY.md](docs/SECURITY.md) | Threat model, auth realms, denylist inventory, approval flow, known limits. |
| [docs/TESTING.md](docs/TESTING.md) | How tests work, fixtures, per-file coverage map. |
| [docs/ROADMAP.md](docs/ROADMAP.md) | Current platform direction, a verified snapshot of what is implemented, and the phased plan (identity, isolation, accounts, memory/context intelligence, dashboard, deployment). |

---

## Known limits (tl;dr)

- Invincible translates Anthropic tool calls correctly (`tool_use` →
  `tool_calls`, `tool_result` → `role: "tool"` messages, ids preserved, and
  responses close with `stop_reason: "tool_use"`), but it does not execute
  the tools itself — execution is the client's job (Claude Code runs the
  tool and sends back `tool_result`).
- Image content blocks are **skipped** during flattening.
- Denylists are **text-pattern matches, not shell parsers** — wrappers like
  `powershell -Command` can smuggle commands past them; the token approval
  step is the real safety boundary.
- **Remote approval** — approval goes through `/mcp` itself: whoever holds
  a valid OAuth Bearer token can approve pending actions; there is no
  terminal prompt and no separate human-authentication surface. Revoke the
  client with `invincible oauth revoke <client_id>` to cut that off.
- Sessions are stored **plaintext** in PostgreSQL (protect the DSN);
  cooldowns and provider disables are **in-memory only**. Since the
  multi-tenant audit, every store path is ownership-scoped per principal —
  one user can never read another's sessions, graph, or history, and the
  former operator override is gone.
- **In-memory server state.** Provider cooldowns, staged approvals (unless
  `INVINCIBLE_PERSIST_PENDING_ACTIONS` is set), and the agent registry live
  in the process, so a remote deployment runs a **single instance** (no
  horizontal autoscaling) and clients simply retry across a restart — see
  [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md).

Full details: [docs/SECURITY.md](docs/SECURITY.md) → *Known limits*.

---

## Development

```bash
pip install -e ".[dev]"
pytest
```

See [docs/TESTING.md](docs/TESTING.md).
