Skip to main content
Glama
README.md
# grokbot-mcp

Outbound MCP server: Cursor, Claude, and VS Code call tools that drive **your** Grok Bot cloud agents. It wraps [`grokbot-client`](https://github.com/Kenzim/grokbot-client) (`import grokbot`) against `https://api2.cursor.sh`.

This is **not** the official Grok MCP and **not** Forge’s inbound `grokbot_mcp_gateway.py`. Those let a Bot call *your* tools. This server is the opposite direction (IDE → Bot). **Never install this MCP on a Grok Bot.**

Not affiliated with Cursor or xAI. The backend is unofficial and can change when the desktop app updates. Bundled client schemas match grok-bot **0.51.0** (`SCHEMA_VERSION`). MIT licensed.

Package: `grokbot_mcp`. CLI: `grokbot-mcp` / `python -m grokbot_mcp`. Default transport is **stdio**. `--http` is Streamable HTTP (`stateless=True`, `json_response=True`). Logs go to stderr only (WARNING; `httpx`/`httpcore` silenced). `PYTHONUNBUFFERED=1`.

Server instructions (what the MCP tells the host): `grokbot_send` is fire-and-forget — it returns `message_id` and `delivery` immediately. Retry a send with the same `message_id`. Use `grokbot_wait` or `grokbot_history` for the reply. If the transcript is quiet, check `grokbot_pending` for widgets or handoff. Only create/delete agents, `put_secret`, or desktop `wake=true` when the user asked.

## Auth

Sign in from the CLI (no Cursor app required). This is Cursor’s PKCE device login:
the CLI prints a `loginDeepControl` URL, you approve it in a browser, and the
process polls `https://api2.cursor.sh/auth/poll` until it gets an access JWT.

```bash
grokbot-mcp login              # prints a URL, opens the browser, waits up to 5 min
grokbot-mcp login --no-open    # URL only (SSH / Docker / headless)
grokbot-mcp login --print-token  # also write the JWT to stdout (avoid on shared terminals)
grokbot-mcp logout             # delete the saved file
```

Credentials are stored mode `0600` at `~/.config/grokbot-mcp/cursor-auth.json`
(or `$GROKBOT_AUTH_FILE`). The MCP process prefers that file, then Cursor host
files, then `$GROKBOT_TOKEN`:

1. grokbot-mcp login file
2. `~/.config/cursor/auth.json`, `~/.cursor/auth.json`, `~/.cursor/cli-config.json`
3. else `$GROKBOT_TOKEN` (raw JWT)

The login JWT is a **session access token**, not a Cursor dashboard API key.
Do not commit it. Compose mounts `~/.config/grokbot-mcp` so a host `login`
works inside the HTTP container.

HTTP also requires `$GROKBOT_MCP_TOKEN` (a shared secret you mint). That token is **not** the Cursor JWT.

## Local venv

Python 3.10+. `grokbot-client` is not on PyPI.

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install "git+https://github.com/Kenzim/grokbot-client.git"
pip install -e ".[dev]"
grokbot-mcp login              # browser PKCE; saves ~/.config/grokbot-mcp/cursor-auth.json
grokbot-mcp                    # stdio
grokbot-mcp --http             # needs GROKBOT_MCP_TOKEN
```

LAN: `pip install "git+https://git.stackken.com/kenzim/grokbot-client.git"` or `pip install -e /path/to/grokbot-client`.

Copy `.env.example` to `.env` and fill values. Compose loads `.env`; stdio does not require it if host files or `GROKBOT_TOKEN` are present.

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

Flags: `--http`, `--host` (default `127.0.0.1`), `--port` (default `8080`), `--path` (default `/mcp`). Env wins if a flag is omitted.

## IDE config — three transports

Use **one** of: venv stdio, `docker run -i` (no `-t`), or HTTP URL + Bearer. Replace paths and tokens.

### Cursor (`~/.cursor/mcp.json`)

Stdio (venv):

```json
{
  "mcpServers": {
    "grokbot": {
      "command": "/ABS/PATH/grokbot-mcp/.venv/bin/grokbot-mcp"
    }
  }
}
```

Stdio (Docker). **Do not pass `-t`** — TTY breaks MCP framing. `-i` is required.

```json
{
  "mcpServers": {
    "grokbot": {
      "command": "docker",
      "args": [
        "run", "-i", "--rm",
        "-e", "GROKBOT_TOKEN",
        "-v", "${HOME}/.config/cursor:/root/.config/cursor:ro",
        "-v", "${HOME}/.cursor:/root/.cursor:ro",
        "git.stackken.com/kenzim/grokbot-mcp:latest"
      ]
    }
  }
}
```

HTTP (after compose is up):

```json
{
  "mcpServers": {
    "grokbot": {
      "url": "http://127.0.0.1:8080/mcp",
      "headers": { "Authorization": "Bearer YOUR_GROKBOT_MCP_TOKEN" }
    }
  }
}
```

### Claude Desktop

Same `mcpServers` object in `claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/`; Windows: `%APPDATA%\Claude\`; Linux: `~/.config/Claude/`). Stdio `command`/`args` as above. HTTP uses `url` + `headers` when the host supports Streamable HTTP.

### VS Code (`.vscode/mcp.json`)

```json
{
  "servers": {
    "grokbot": {
      "type": "stdio",
      "command": "/ABS/PATH/grokbot-mcp/.venv/bin/grokbot-mcp"
    }
  }
}
```

Docker stdio: `"command": "docker", "args": ["run", "-i", "--rm", ...]` (no `-t`). HTTP:

```json
{
  "servers": {
    "grokbot": {
      "type": "http",
      "url": "http://127.0.0.1:8080/mcp",
      "headers": { "Authorization": "Bearer YOUR_GROKBOT_MCP_TOKEN" }
    }
  }
}
```

### Claude Code

`claude mcp add grokbot -- /ABS/PATH/.venv/bin/grokbot-mcp` (stdio), or `claude mcp add --transport http grokbot http://127.0.0.1:8080/mcp` plus an Authorization header / env for Bearer. Project file `.mcp.json` uses the same three shapes as Cursor.

## Docker

Image: `python:3.11-slim-bookworm`. Build arg `GROKBOT_CLIENT_GIT` defaults to `https://git.stackken.com/kenzim/grokbot-client.git`. Public: `--build-arg GROKBOT_CLIENT_GIT=https://github.com/Kenzim/grokbot-client.git`. `ENTRYPOINT` is `python -m grokbot_mcp`; default CMD is empty (**stdio**).

Stdio container (host MCP spawns it). Interactive stdin, **no TTY**:

```bash
docker build -t grokbot-mcp .
docker run -i --rm \
  -e GROKBOT_TOKEN \
  -v "$HOME/.config/cursor:/root/.config/cursor:ro" \
  grokbot-mcp
```

## Compose HTTP

```bash
cp .env.example .env   # set GROKBOT_MCP_TOKEN (required) and GROKBOT_TOKEN if no host files
docker compose up --build
```

Service `grokbot-mcp` runs `--http --host 0.0.0.0`, publishes `127.0.0.1:8080:8080`, volume `/data` (cursors), optional `~/.config/cursor:ro`, `env_file: .env`. HEALTHCHECK curls `/health`.

- `GET /health` — unauthenticated `{"ok": true}`
- `/mcp` — `Authorization: Bearer <GROKBOT_MCP_TOKEN>`; missing/wrong token → 401
- HTTP **refuses to start** without `GROKBOT_MCP_TOKEN`
- Rate limit: 60 requests/min per token (override `GROKBOT_MCP_RATE_PER_MIN`)

The HTTP process starts the in-process transcript watcher immediately. Stdio starts it lazily on first `wait` / `pending` / resource read.

## Tools

Every result is one MCP text blob: JSON `{"ok": true, ...}` or `{"ok": false, "error": "...", "code": "..."}`. Codes map `UnauthorizedError` / `AuthError` / `RefusalError` / `NotFoundError` / `GrokBotError`. Protobuf `.raw` is never serialized. Default `session_id` is `""` (MAIN). Optional `session_id` on chat/live tools.

Reads have `readOnlyHint`. `grokbot_delete_*`, `grokbot_put_secret`, and `grokbot_desktop` with `wake` have `destructiveHint`. `grokbot_send` is mutating and idempotent if `message_id` is reused.

### Roster

- **grokbot_whoami** — `email`, `user_id`, `SCHEMA_VERSION`
- **grokbot_list_agents** — `id`, `agent_id`, `name`, `description`, `harness`
- **grokbot_get_agent** — list row plus `todos`, `sessions`, `capabilities` (`agent_id` required)
- **grokbot_create_agent** — `name` required; `harness` `box|temporal` (default `box`); optional `description`, `title`. Only if the user asked.
- **grokbot_update_agent** — `agent_id`; optional `name`, `description`, `title`, `avatar_shape`, `avatar_color`
- **grokbot_delete_agent** — preview unless `confirm=true` (does not call delete on preview)

### Chat — send vs wait

- **grokbot_send** — fire-and-forget. Args: `agent_id`, `text`; optional `session_id`, `message_id`, `rich_text`, `reply_to_id`, `attachments`, `fork`. Returns `message_id` + `delivery` immediately (`ACCEPTED_BOX`, `ACCEPTED_TEMPORAL`, `DUPLICATE`, …). It does **not** wait for the assistant. Retry by sending the same `message_id`.
- **grokbot_wait** — block until the next assistant-ish transcript entry after send, the agent stops (`is_running` false), or timeout (default 45s from `GROKBOT_WAIT_TIMEOUT_SEC`, clamp 1–120). Returns `{timed_out, entries, is_running}`. Honours MCP cancellation. Does **not** use MCP Tasks. Polls history if the watcher is down.
- **grokbot_status** — `GetGrokBotSendStatus` for `message_id`
- **grokbot_history** — default limit 40, max 100; optional `before_seq`. Entries use Forge `entry_public` shape: `seq`, `entry_id`, `entry_kind`, `role`, `text`, `ts_ms`, `widget`
- **grokbot_interrupt** — returns `had_active_run`
- **grokbot_draft** / **grokbot_discard_draft** / **grokbot_react** — wrap `ChatSession`. Draft needs **exactly one** of `email` or `slack` object.

Typical loop: `send` → `wait` (or `history` if you already know `after_seq`) → if quiet, `pending`.

### Secrets

- **grokbot_list_secrets** — names + descriptions only
- **grokbot_put_secret** — `agent_id`, `name`, `value`; optional `description`. Result `{ok, name}` only. Value is never echoed in logs or the result. Only if the user asked.
- **grokbot_delete_secret** — `confirm=true` required or preview-only

### Widgets, handoff, desktop

- **grokbot_pending** — widgets + `HandoffRequested`. Drops stale items older than 6 hours. Optional `agent_id` / `session_id`.
- **grokbot_resolve_widget** — `action`: `respond` | `dismiss` | `form` | `secret` | `approve`. Requires `agent_id` + `entry_id`. Payload: `value` (respond/secret), `values` object (form), `approved` (approve).
- **grokbot_desktop** — account sandbox (not per-agent; `temporal` has none). `wake` default false. `wake=true` requires `confirm=true` (may boot a hibernated box). Returns `run_state`, `connect_url`, and websocket **header names** (not token values). No in-process RFB/noVNC.
- **grokbot_end_handoff** — `client.end_handoff(agent_id, request_id)`; optional `trigger` (default `DISMISSED`)

Resources (JSON): `grokbot://me`, `grokbot://agents`, `grokbot://agents/{id}/transcript`, `grokbot://agents/{id}/pending`. Watcher persists Forge-shaped cursors in `$GROKBOT_DATA_DIR/grokbot_cursors.json` (default `./data`, Docker `/data`). Stdio process death is fine.

## Environment

Copy `.env.example` → `.env`. Empty values mean “unset / default”.

- `GROKBOT_TOKEN` — Cursor JWT if no `auth.json`
- `GROKBOT_MCP_TOKEN` — required for HTTP; Bearer shared secret
- `GROKBOT_MCP_HOST` / `GROKBOT_MCP_PORT` / `GROKBOT_MCP_PATH` — HTTP bind (defaults `127.0.0.1`, `8080`, `/mcp`)
- `GROKBOT_MCP_RATE_PER_MIN` — default 60
- `GROKBOT_DATA_DIR` — default `./data` (Docker `/data`)
- `GROKBOT_AUTH_FILE` — override PKCE credential path (default `~/.config/grokbot-mcp/cursor-auth.json`)
- `GROKBOT_WAIT_TIMEOUT_SEC` — default 45, clamp 1..120

## Security

- HTTP will not listen without `GROKBOT_MCP_TOKEN`. Compare Bearer with `hmac.compare_digest`. `/health` is the only unauthenticated route.
- Bind compose to localhost (`127.0.0.1:8080`). Treat the MCP token like a password.
- Do not install this server on Grok Bots (inbound vs outbound).
- Do not log JWT, MCP token, secret `value`, or desktop header **values**. Desktop returns header **names** only; `put_secret` / widget `secret` never echo the secret. `login` does not print the JWT unless `--print-token`.
- `delete_*` without `confirm=true` is preview-only. Desktop `wake=true` needs `confirm=true`. Create/delete/`put_secret`/wake only when the user asked.

## Tests

Offline (FakeClient, no network). Pytest `asyncio_mode=auto`, `--cov=grokbot_mcp --cov-fail-under=70`.

```bash
ruff check grokbot_mcp tests
pytest -q
```

Markers: `live` / `live_mutate` skip unless env is set. **`GROKBOT_LIVE_MUTATE=1` creates and sends on the production backend — throwaway agent only.** `GROKBOT_LIVE=1` is read-mostly (GetMe, list, watch, desktop `wake=false`).

## CI (Forgejo)

Workflows under `.forgejo/workflows/` (`ci`, `sonarqube`, `owasp`, `container`). No `.github/workflows` (GitHub is a push mirror). Jobs `runs-on: docker` with LAN checkout of this repo and `grokbot-client`. `sonar.projectKey=kenzim_grokbot-mcp`. OWASP DC JSON is **not** imported into Sonar.

Repository secrets:

- `SONAR_HOST_URL` — SonarQube base URL
- `SONAR_TOKEN` — analysis token
- `REGISTRY_USER` plus `REGISTRY_TOKEN` or `PACKAGE_WRITABLE_TOKEN` — push `git.stackken.com/kenzim/grokbot-mcp:{sha,latest,tag}`

Out of scope: Forge inbound gateway, Electron keychain, usage/billing RPCs, MCP Tasks, in-process VNC, PyPI publish.

TDQS

C2.7/5.0

Scored across 21 tools

Disambiguation4/5

Most tools target distinct resources or actions, and the agent and secret CRUD groups are clearly separated. Some overlap exists among send/status/wait/draft and between pending/history, but descriptions provide enough context to choose correctly.

Naming Consistency3/5

All tool names share the grokbot_ prefix and snake_case, which is consistent. However, the set mixes verb_noun patterns (list_agents, create_agent, resolve_widget) with bare nouns/verbs (status, history, wait, desktop, whoami), making the convention less predictable.

Tool Count3/5

21 tools is on the heavy side for a single server, even though the capabilities span agents, messaging, widgets, secrets, desktop, and handoff. Each area is distinct, but some niche operations could likely be consolidated.

Completeness4/5

Core CRUD for agents and secrets, messaging, widget handling, desktop access, and handoff are all covered. Minor gaps remain, such as todo/session management, draft listing/approval, and transcript search, but the main workflows are supported.

Maintenance

ActivityMaintained
ResponsivenessNo issues