# MCP install — Rivalatch Ship

Public name **Rivalatch**. Entry points, env, and packages stay VibeGate (`vibegate-mcp`, `VIBEGATE_API_KEY`, path `/mcp`). Spec untouched.

Thin card for connecting agents to the **shared ship door**. Not a sandbox console.

Transports: **stdio MCP** (`vibegate-mcp`) and **optional MCP-over-HTTPS** (isomorphic). Same three frozen tools as HTTP. Annotations allowed on descriptors; zero GateResult / inputSchema field invention.

| Tool | HTTP |
|------|------|
| `gate.run` | `POST /v1/gate/run` |
| `gate.status` | `GET /v1/gate/status/{job_id}` |
| `gate.get_result` | `GET /v1/gate/results/{job_id}` |

Entry points (after wheel or editable install of Ship + Runner):

```bash
vibegate-mcp                          # console script → vibegate_ship.mcp_stdio:main
python -m vibegate_ship.mcp_stdio     # same stdio JSON-RPC loop
```

Env card for the MCP process (match AGENT.md):

| Env | Required | Notes |
|-----|----------|-------|
| `VIBEGATE_API_KEY` | yes | Sent as `X-Api-Key`. Local spike: `dev-key`. |
| `VIBEGATE_SANDBOX` | no | Default `mock` when unset. |

MCP-over-HTTPS is an **optional** isomorphic transport peer to stdio and REST (`/v1/gate/*`):

| Transport | How |
|-----------|-----|
| stdio | `vibegate-mcp` / `python -m vibegate_ship.mcp_stdio` |
| HTTPS | `POST /mcp` with JSON-RPC (same `dispatch` as stdio); `X-Api-Key` |

**stdio remains** for local agents; remote MCP is additive. Same three tools only.

Directory ops (not schema): `GET /.well-known/openai-apps-challenge` → plain text from `VIBEGATE_OPENAI_APPS_CHALLENGE` (404 if unset).

Descriptors: `mcp/tools.json`. Stub map: `mcp/server-stub.md`. Agent card: [`AGENT.md`](./AGENT.md).

---

## Prerequisite

Install Ship + Runner once (wheels preferred; no chat history):

```bash
python3 -m venv .venv && . .venv/bin/activate
pip install -U pip build
(cd vibegate-runner && python -m build -w)
(cd vibegate-ship && python -m build -w)
pip install \
  vibegate-runner/dist/vibegate_runner-0.1.0-py3-none-any.whl \
  vibegate-ship/dist/vibegate_ship-0.1.0-py3-none-any.whl
export VIBEGATE_API_KEY=dev-key
# export VIBEGATE_SANDBOX=mock   # optional
which vibegate-mcp   # use this absolute path in client configs
```

Replace `/ABS/PATH/TO/vibegate-mcp` below with that path (or the venv’s `python` + `-m vibegate_ship.mcp_stdio`).

---

## Cursor

Project: `.cursor/mcp.json` · global: `~/.cursor/mcp.json`

```json
{
  "mcpServers": {
    "vibegate": {
      "type": "stdio",
      "command": "/ABS/PATH/TO/vibegate-mcp",
      "env": {
        "VIBEGATE_API_KEY": "dev-key",
        "VIBEGATE_SANDBOX": "mock"
      }
    }
  }
}
```

`VIBEGATE_SANDBOX` is optional (omit or leave unset for default mock). Or: `"command": "/ABS/PATH/TO/python"`, `"args": ["-m", "vibegate_ship.mcp_stdio"]`. Reload Cursor after save.

---

## Claude Code

CLI:

```bash
claude mcp add --transport stdio --env VIBEGATE_API_KEY=dev-key vibegate \
  -- /ABS/PATH/TO/vibegate-mcp
```

Project share (commit `.mcp.json`):

```json
{
  "mcpServers": {
    "vibegate": {
      "command": "/ABS/PATH/TO/vibegate-mcp",
      "env": {
        "VIBEGATE_API_KEY": "dev-key",
        "VIBEGATE_SANDBOX": "mock"
      }
    }
  }
}
```

`VIBEGATE_SANDBOX` optional. Check: `claude mcp get vibegate` / `/mcp`.

---

## Codex

Global `~/.codex/config.toml` (or trusted project `.codex/config.toml`):

```toml
[mcp_servers.vibegate]
command = "/ABS/PATH/TO/vibegate-mcp"

[mcp_servers.vibegate.env]
VIBEGATE_API_KEY = "dev-key"
# VIBEGATE_SANDBOX = "mock"   # optional
```

CLI alternative: `codex mcp add vibegate --env VIBEGATE_API_KEY=dev-key -- /ABS/PATH/TO/vibegate-mcp`

Verify in session with `/mcp`.

---

## Grok Build

CLI:

```bash
grok mcp add vibegate --env VIBEGATE_API_KEY=dev-key -- /ABS/PATH/TO/vibegate-mcp
```

Or `~/.grok/config.toml` / project `.grok/config.toml`:

```toml
[mcp_servers.vibegate]
command = "/ABS/PATH/TO/vibegate-mcp"
env = { VIBEGATE_API_KEY = "dev-key" }  # optional: VIBEGATE_SANDBOX = "mock"
```

Doctor: `grok mcp doctor vibegate`. Grok also merges Cursor/Claude MCP files if present.

---

## Knock once (via tools)

1. `gate.run` — minimal body from [`AGENT.md`](./AGENT.md) (`app_ref` / `checks` / `acceptance` / `idempotency_key`).
2. `gate.status` — `{ "job_id": "…" }`.
3. `gate.get_result` — same `job_id`; terminal GateResult only (no `compose_source` / `hold_readings`).

Rival second knock with a different payload on the same `idempotency_key` → **409** held|incoming. Shared door, not a private sandbox.

## Mobile knocker (thin)

Share/deep-link paste → decode `rivalatch://knock?…` → same three tools (`gate.run` / `gate.status` / `gate.get_result`). See [`MOBILE_KNOCKER.md`](./MOBILE_KNOCKER.md). Spec untouched; no store app.
