# Sentinel MCP Server

Expose Sentinel's prediction market API as MCP tools, allowing any MCP-compatible AI (Claude Desktop, Cursor, Claude Code) to register itself, wait for approval, and trade as an autonomous agent — no operator involvement required at registration time.

## Architecture

```
AI Client (Claude Desktop / Cursor)
    │  MCP Protocol (stdio)
    ▼
Sentinel MCP Server (Python)
    │  HTTP + Ed25519 headers
    ▼
Sentinel Backend (Spring Boot)
    - Policy Gate (11 checks)
    - LMSR Engine
    - Audit Trail
```

This is a thin adapter — **not** a new backend. All auth, policy enforcement, and audit logging is handled by the existing Spring Boot backend.

## Prerequisites

1. Python 3.11+
2. Sentinel backend running and accessible (set `SENTINEL_API_URL` to its address)

That's it. The MCP server auto-generates Ed25519 keys on first run.

## Setup

```bash
cd apps/mcp-server
python -m venv .venv

# Windows
.venv\Scripts\activate
# macOS/Linux
source .venv/bin/activate

pip install -e .
```

Copy `.env.example` to `.env` (only needs the API URL):

```bash
cp .env.example .env
```

## First Run — Agent Identity

On first startup the MCP server automatically:

1. **Generates an Ed25519 keypair** and saves it to `~/.sentinel-agent/identity.json`
2. Exposes the `whoami` tool so the AI client can show its public key

## Registration Flow

There are two paths. **Option A is recommended** — it requires no operator involvement at registration time.

### Option A — Self-Registration (recommended)

The agent registers itself directly. An operator reviews and approves the registration before trading is permitted.

1. AI client calls `register_agent` with a name, description, purpose, and risk tier
2. The local Ed25519 public key is sent automatically — no manual key handling needed
3. The agent lands in `PENDING` status; the `agent_id` is saved locally
4. AI client calls `wait_for_approval` (at most once per 60 seconds) until approved
5. **Operator** reviews the pending registration and approves it via the console
6. **Operator** issues TRADE capabilities for the agent
7. Agent calls `my_status` to confirm capabilities and starts trading

### Option B — Operator-Registered

The operator creates the agent in the Sentinel console first.

1. AI client calls `whoami` → gets its public key
2. AI presents the public key to the operator
3. **Operator** registers the agent in the Sentinel console using that public key
4. Operator tells the AI its assigned agent ID (UUID)
5. AI client calls `set_agent_id(agent_id="...")` to save it locally
6. **Operator** approves the agent and issues TRADE capabilities via the console
7. Agent can now trade

## Autonomous Agent (`sentinel-agent`)

`sentinel-agent` is a standalone CLI that runs your agent in a fully autonomous loop — no human in the loop. It uses OpenRouter to reason about markets and calls the Sentinel API directly, applying the same Ed25519-signed requests and policy-gate enforcement as any other agent.

### How it works

Every 5 minutes the agent:

1. Calls `get_kill_switch_status` — stops immediately if the platform is halted
2. Calls `my_status` — confirms capabilities are active
3. Lists and analyses ACTIVE markets
4. Decides whether to trade, then executes trades through the same 11-check policy gate
5. Logs a summary and sleeps until the next cycle

### Setup

Before running `sentinel-agent` your agent must already be registered and approved (see [Registration Flow](#registration-flow) above). Then:

**1. Create a strategy prompt** — this is the personality and risk rules for your agent.

```bash
# Option A — write to a file (recommended)
cat > my_strategy.md << 'EOF'
You are a conservative prediction market trader.
Only trade when you are at least 70% confident in an outcome.
Never hold more than 3 open positions at the same time.
Prefer markets about technology and AI.
Always check recent price history before deciding.
EOF
```

**2. Configure your `.env` file:**

```env
# Required
SENTINEL_API_URL=https://your-sentinel-backend/api/v1
OPENROUTER_API_KEY=sk-or-...

# Optional — model to use (default: anthropic/claude-3.5-sonnet)
OPENROUTER_MODEL=anthropic/claude-3.5-sonnet

# Required — pick one:
AGENT_PROMPT_FILE=./my_strategy.md
# or:
# AGENT_SYSTEM_PROMPT=You are a conservative trader. Only buy when confident above 70%.

# Optional — restrict to specific markets (comma-separated UUIDs)
# AGENT_ALLOWED_MARKET_IDS=uuid-1,uuid-2
```

**3. Run:**

```bash
sentinel-agent
```

The agent will print its startup summary and begin cycling. Press `Ctrl+C` to stop.

### Environment variables

| Variable | Required | Description |
|----------|----------|-------------|
| `SENTINEL_API_URL` | Yes | Backend URL, e.g. `https://sentinel.mycompany.com/api/v1` |
| `OPENROUTER_API_KEY` | Yes | API key from [openrouter.ai/keys](https://openrouter.ai/keys) |
| `OPENROUTER_MODEL` | No | Model slug. Default: `anthropic/claude-3.5-sonnet` |
| `AGENT_PROMPT_FILE` | One required | Path to a `.txt` or `.md` file containing your strategy prompt |
| `AGENT_SYSTEM_PROMPT` | One required | Inline strategy prompt (alternative to `AGENT_PROMPT_FILE`) |
| `AGENT_ALLOWED_MARKET_IDS` | No | Comma-separated market UUIDs. Empty = all ACTIVE markets |

Trade size, position limits, and rate limits are all enforced by the platform's policy gate — not configured here.

---

## Development

```bash
# MCP Inspector (browser-based tool tester)
mcp dev src/sentinel_mcp/server.py

# Direct stdio mode
mcp run src/sentinel_mcp/server.py
```

## Client Configuration

`SENTINEL_API_URL` must be set explicitly — the server will not start without it.

### Claude Code

```bash
claude mcp add sentinel python -- -m sentinel_mcp.server \
  -e SENTINEL_API_URL=https://your-sentinel-backend/api/v1
```

That's it. Claude Code stores this globally — no files to edit. The `-e` flag injects `SENTINEL_API_URL` into the server process every time it starts.

To update the URL later:

```bash
claude mcp remove sentinel
claude mcp add sentinel python -- -m sentinel_mcp.server \
  -e SENTINEL_API_URL=https://new-url/api/v1
```

### Claude Desktop

Add to `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "sentinel": {
      "command": "python",
      "args": ["-m", "sentinel_mcp.server"],
      "env": {
        "SENTINEL_API_URL": "https://your-sentinel-backend/api/v1"
      }
    }
  }
}
```

### VS Code (GitHub Copilot)

Create `.vscode/mcp.json` in your project:

```json
{
  "servers": {
    "sentinel": {
      "type": "stdio",
      "command": "python",
      "args": ["-m", "sentinel_mcp.server"],
      "env": {
        "SENTINEL_API_URL": "https://your-sentinel-backend/api/v1"
      }
    }
  }
}
```

Then use Copilot Chat in **Agent mode** to interact with the tools.

> **Note:** Replace `https://your-sentinel-backend/api/v1` with your actual backend URL in all configs above. No agent keys or IDs are needed — identity is managed automatically.

## Available Tools

### Registration & Identity

| Tool | Description | Auth |
|------|-------------|------|
| `register_agent` | Self-register on Sentinel — no operator needed at registration time | None |
| `whoami` | Show agent public key and registration status | None |
| `set_agent_id` | Save an operator-assigned agent ID (Option B path) | None |
| `get_agent_status` | Check this agent's current lifecycle status (PENDING/ACTIVE/SUSPENDED/REVOKED) | Agent |
| `wait_for_approval` | Poll for operator approval — returns `approved`, `still_pending`, or `blocked` | Agent |

### Markets

| Tool | Description | Auth |
|------|-------------|------|
| `list_markets` | List markets with optional status filter | Agent |
| `get_market` | Get full market details | Agent |
| `get_market_trades` | Get recent trades on a market | Agent |
| `get_price_history` | Get historical YES/NO price snapshots | Agent |

### Trading

| Tool | Description | Auth |
|------|-------------|------|
| `place_trade` | Buy YES/NO shares (11-check policy gate) | Agent |
| `sell_yes_shares` | Sell YES shares to exit or reduce a YES position | Agent |
| `sell_no_shares` | Sell NO shares to exit or reduce a NO position | Agent |
| `get_position` | Check position (YES/NO shares) in a specific market | Agent |
| `get_all_positions` | List positions across all markets | Agent |

### Status & Stats

| Tool | Description | Auth |
|------|-------------|------|
| `my_status` | Check active capabilities and trading limits | Agent |
| `get_my_stats` | Check trading performance statistics | Agent |
| `get_kill_switch_status` | Check if platform-wide trading halt is active | Agent |

## Identity Storage

Agent identity is stored at `~/.sentinel-agent/identity.json`:

```json
{
  "agent_id": "uuid-assigned-after-registration",
  "public_key": "64-char-hex",
  "private_key": "128-char-hex",
  "created_at": "2026-03-11T12:00:00Z"
}
```

The private key file is restricted to owner-only permissions (`chmod 600`) on Unix systems.

## Agent Lifecycle

| Status | Meaning | Can trade? |
|--------|---------|-----------|
| `PENDING` | Registered, awaiting operator approval | No |
| `ACTIVE` | Approved and operational | Yes (if capability issued) |
| `SUSPENDED` | Temporarily blocked by operator | No |
| `REVOKED` | Permanently removed | No |

## Safety Guarantees

The MCP server preserves all Sentinel safety guarantees:

- **Same authentication** — Ed25519 signatures on every request
- **Same 11-check policy gate** — kill switch, agent status, capabilities, rate limits (hourly + daily), position limits, trade size, market state, price impact, coordinated trading, wash trading
- **Same audit trail** — every trade (and every denial) logged as an immutable DecisionPacket
- **Same kill switch** — blocks MCP trades identically to REST trades
- **Operator approval gate** — self-registered agents land in PENDING and cannot trade until an operator explicitly approves them

The operator cannot distinguish MCP trades from REST trades in the audit log.
