---
title: Connect MCP and CLI
description: Connect an OAuth MCP client or the Rinhelp CLI and use every current operator operation.
sidebar:
  label: MCP and CLI
  order: 3
---

Connect an MCP client or use the Rinhelp CLI to manage your Rinhelp workspace. Both expose the operations listed below. Your access token selects the workspace; never pass `organization_id`.

## Connect MCP

Add `https://mcp.rinhelp.com/mcp` as a remote HTTP MCP server in a client that supports OAuth. Complete the browser authorization for the `rinhelp.operator` scope.

After connecting, call `account.whoami` and confirm the returned organization and role before making changes. MCP clients discover the current operation list through `tools/list`; the reference on this page is generated from that same shared catalog.

Legacy `/sse` connections are unsupported. Use the `/mcp` endpoint and a client with OAuth support.

## Use the CLI

The current CLI is shipped in the [Rinhelp repository](https://github.com/wilbertliu/rinhelp/tree/main/apps/cli), not published as an npm package. From a source checkout, install the workspace dependencies and run the CLI entry point:

```sh
git clone https://github.com/wilbertliu/rinhelp.git
cd rinhelp
pnpm install
pnpm --filter @rinhelp/cli exec node bin/rinhelp.mjs login
```

`login` opens browser OAuth, asks for the `rinhelp.operator` scope, and stores credentials locally. Stored credentials refresh when the saved credential contains refresh metadata. Use another endpoint with `--mcp`:

```sh
pnpm --filter @rinhelp/cli exec node bin/rinhelp.mjs login --mcp https://mcp.example.com/mcp
```

For the examples below, run the command with `pnpm --filter @rinhelp/cli exec node bin/rinhelp.mjs` from the checkout, or make that entry point available as `rinhelp` on your `PATH`.

```sh
rinhelp --json account.whoami
rinhelp ops
rinhelp sources.start_crawl --help
rinhelp --json inbox.list_items --input '{}'
```

Use exactly one input style per command:

- `--input '<json object>'` for inline JSON.
- `--input-file <path>` for a file, or `--input-file -` for stdin.
- Field flags such as `--conversation-id` and `--idempotency-key`.

Do not mix input JSON with field flags. `rinhelp logout` removes saved credentials. `RINHELP_ACCESS_TOKEN` can supply an access token instead of saved credentials; that environment value does not carry the saved issuer/client metadata used for automatic refresh.

The CLI returns exit code `0` for success, `1` for another operator or transport failure, `2` for invalid input or usage, `3` when login is required or a credential is expired, and `4` when the operation is forbidden. With `--json`, successful calls print `{ "ok": true, "op": "...", "output": ... }`; operator errors print `{ "ok": false, "error": { "code": "...", "message": "..." } }`. Unexpected runtime failures can still write to stderr and exit `1`.

## Operation behavior to know first

- Crawls return `run_id` immediately. Pass it as `source_id` to `sources.show` and poll until ready or failed.
- `widget.preview` returns one completed ephemeral answer, not a stream.
- `email.setup` returns the exact PostShiba forwarding destination. The merchant keeps the support mailbox, configures forwarding, and keeps a mailbox copy; Rinhelp does not change MX. `email.verify` sends a test, then `email.show` reports whether forwarding is verified or failed.
- `inbox.list` lists conversations. `inbox.list_items` includes conversations and pending refunds. Pass `control=closed` to select closed work.
- `inbox.reply` posts a teammate reply. The first reply claims the thread; it does not impersonate the visitor.
- Refund requests do not execute refunds. Obtain a deliberate human decision before calling `inbox.approve_action`; that call executes a pending Shopify refund. `inbox.reject_action` rejects it without executing. Get `action_id` from the refund item's `refund.id` in `inbox.list_items`.
- Generate one `idempotency_key` per intent and reuse it on retry with the same input. The CLI does not generate keys. For a failed refund approval, reuse the same key; do not retry an unconfirmed Shopify result.
- `team.invite` and `team.cancel_invite` require the `owner` role. The role required by every operation appears below.

## Current operations

This reference contains all 23 current operations (1 account, 4 sources, 3 widget, 3 email, 7 inbox, 3 team, 2 shopify). The names are the same in MCP and the CLI. It is generated from the shared operator catalog, so this page does not document a second API.

### `account.whoami`

Return the signed-in operator (user, organization, role).

- **Role:** `member`
- **Retry:** `safe`

#### Input schema

```json
{
  "type": "object",
  "additionalProperties": false,
  "properties": {}
}
```

### `sources.list`

List crawl/index runs for this organization.

- **Role:** `member`
- **Retry:** `safe`

#### Input schema

```json
{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "cursor": {
      "type": "string"
    },
    "limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 100
    }
  }
}
```

### `sources.start_crawl`

Start a site crawl (async). Returns run_id immediately. Poll sources.show. Same idempotency_key + url + org replays the same run.

- **Role:** `member`
- **Retry:** `idempotency_key`

#### Input schema

```json
{
  "type": "object",
  "additionalProperties": false,
  "required": ["url", "idempotency_key"],
  "properties": {
    "url": {
      "type": "string",
      "format": "uri"
    },
    "idempotency_key": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    }
  }
}
```

### `sources.show`

Get one ingest run and its pages. Crawl is async; poll until ready or failed.

- **Role:** `member`
- **Retry:** `safe`

#### Input schema

```json
{
  "type": "object",
  "additionalProperties": false,
  "required": ["source_id"],
  "properties": {
    "source_id": {
      "type": "string",
      "minLength": 1
    }
  }
}
```

### `sources.show_page`

Get one crawled page and its chunks.

- **Role:** `member`
- **Retry:** `safe`

#### Input schema

```json
{
  "type": "object",
  "additionalProperties": false,
  "required": ["page_id"],
  "properties": {
    "page_id": {
      "type": "string",
      "minLength": 1
    }
  }
}
```

### `widget.show`

Widget config, publishable key, and install snippet for this org.

- **Role:** `member`
- **Retry:** `safe`

#### Input schema

```json
{
  "type": "object",
  "additionalProperties": false,
  "properties": {}
}
```

### `widget.update_config`

Patch widget config (greeting, title, colors, position). Omitted fields keep their current values.

- **Role:** `member`
- **Retry:** `idempotent`

#### Input schema

```json
{
  "type": "object",
  "additionalProperties": false,
  "minProperties": 1,
  "properties": {
    "primary_color": {
      "type": "string"
    },
    "position": {
      "type": "string",
      "enum": ["bottom-right", "bottom-left"]
    },
    "title": {
      "type": "string"
    },
    "greeting": {
      "type": "string"
    }
  }
}
```

### `widget.preview`

Run one ephemeral preview question against the corpus. Returns the completed answer, not a stream.

- **Role:** `member`
- **Retry:** `safe`

#### Input schema

```json
{
  "type": "object",
  "additionalProperties": false,
  "required": ["message"],
  "properties": {
    "message": {
      "type": "string",
      "minLength": 1,
      "maxLength": 4000
    }
  }
}
```

### `email.show`

Show the merchant support address, exact PostShiba forwarding target, and setup state. This does not change MX or send mail.

- **Role:** `member`
- **Retry:** `safe`

#### Input schema

```json
{
  "type": "object",
  "additionalProperties": false,
  "properties": {}
}
```

### `email.setup`

Create or reuse the PostShiba receive target for support_email. The merchant keeps the mailbox and forwards into the returned address; Rinhelp does not change MX.

- **Role:** `member`
- **Retry:** `idempotent`
- **Hint:** open-world operation.

#### Input schema

```json
{
  "type": "object",
  "additionalProperties": false,
  "required": ["support_email"],
  "properties": {
    "support_email": {
      "type": "string",
      "minLength": 1
    }
  }
}
```

### `email.verify`

Send one forwarding test to the configured support address. This starts verification; call email.show until status is verified. Requires idempotency_key and reuse it on retry.

- **Role:** `member`
- **Retry:** `idempotency_key`
- **Hint:** open-world operation.

#### Input schema

```json
{
  "type": "object",
  "additionalProperties": false,
  "required": ["idempotency_key"],
  "properties": {
    "idempotency_key": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    }
  }
}
```

### `inbox.list`

List visitor conversations in the shared inbox. Default is queued and live chats. Pass control=closed to list resolved live chats. Use inbox.list_items for mixed inbox items.

- **Role:** `member`
- **Retry:** `safe`

#### Input schema

```json
{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "cursor": {
      "type": "string"
    },
    "limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 100
    },
    "control": {
      "type": "string",
      "enum": ["closed"]
    }
  }
}
```

### `inbox.list_items`

List the inbox work queue (conversations and pending refunds), the same items as the console. Default is the open queue. Pass control=closed for resolved chats and closed refunds. Use a refund_action item's refund.id as action_id for inbox.approve_action or inbox.reject_action.

- **Role:** `member`
- **Retry:** `safe`

#### Input schema

```json
{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "cursor": {
      "type": "string"
    },
    "limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 100
    },
    "control": {
      "type": "string",
      "enum": ["closed"]
    }
  }
}
```

### `inbox.show`

Get one conversation and its messages. Poll this instead of opening a live WebSocket.

- **Role:** `member`
- **Retry:** `safe`

#### Input schema

```json
{
  "type": "object",
  "additionalProperties": false,
  "required": ["conversation_id"],
  "properties": {
    "conversation_id": {
      "type": "string",
      "minLength": 1
    }
  }
}
```

### `inbox.reply`

Post a teammate reply (first reply claims the thread). Requires idempotency_key. Does not impersonate the visitor.

- **Role:** `member`
- **Retry:** `idempotency_key`

#### Input schema

```json
{
  "type": "object",
  "additionalProperties": false,
  "required": ["conversation_id", "message", "idempotency_key"],
  "properties": {
    "conversation_id": {
      "type": "string",
      "minLength": 1
    },
    "message": {
      "type": "string",
      "minLength": 1,
      "maxLength": 4000
    },
    "idempotency_key": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    }
  }
}
```

### `inbox.end_live`

End live chat on a conversation.

- **Role:** `member`
- **Retry:** `idempotent`

#### Input schema

```json
{
  "type": "object",
  "additionalProperties": false,
  "required": ["conversation_id"],
  "properties": {
    "conversation_id": {
      "type": "string",
      "minLength": 1
    }
  }
}
```

### `team.show`

List members. Pending invitations included only for owners (same as the console).

- **Role:** `member`
- **Retry:** `safe`

#### Input schema

```json
{
  "type": "object",
  "additionalProperties": false,
  "properties": {}
}
```

### `team.invite`

Invite a member by email (owners only, role=member only). A pending invite for the same org+email is returned, not duplicated.

- **Role:** `owner`
- **Retry:** `idempotent`

#### Input schema

```json
{
  "type": "object",
  "additionalProperties": false,
  "required": ["email"],
  "properties": {
    "email": {
      "type": "string",
      "minLength": 1
    }
  }
}
```

### `team.cancel_invite`

Cancel a pending invitation (owners only).

- **Role:** `owner`
- **Retry:** `idempotent`
- **Hint:** destructive operation.

#### Input schema

```json
{
  "type": "object",
  "additionalProperties": false,
  "required": ["invitation_id"],
  "properties": {
    "invitation_id": {
      "type": "string",
      "minLength": 1
    }
  }
}
```

### `shopify.request_refund`

Request a Shopify refund for an order (lookup only; does not execute). Requires idempotency_key. Reuse the same key to replay the same request. Amount omitted requests the full refundable balance.

- **Role:** `member`
- **Retry:** `idempotent`

#### Input schema

```json
{
  "type": "object",
  "additionalProperties": false,
  "required": ["order_number", "email", "idempotency_key"],
  "properties": {
    "order_number": {
      "type": "string",
      "minLength": 1
    },
    "email": {
      "type": "string",
      "minLength": 1
    },
    "amount": {
      "type": "string",
      "minLength": 1
    },
    "reason": {
      "type": "string",
      "minLength": 1
    },
    "idempotency_key": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    }
  }
}
```

### `shopify.lookup_order`

Look up a Shopify order by order number and email. Returns the widget status and tracking, or not_found / shop_unavailable. Requires idempotency_key. Reuse the same key to replay the same lookup.

- **Role:** `member`
- **Retry:** `idempotency_key`

#### Input schema

```json
{
  "type": "object",
  "additionalProperties": false,
  "required": ["order_number", "email", "idempotency_key"],
  "properties": {
    "order_number": {
      "type": "string",
      "minLength": 1
    },
    "email": {
      "type": "string",
      "minLength": 1
    },
    "idempotency_key": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    }
  }
}
```

### `inbox.approve_action`

Approve a pending refund action and execute the Shopify refund. List inbox.list_items first and pass the refund.id as action_id. Reuse the same idempotency_key if the refund failed. Do not retry an unconfirmed Shopify result.

- **Role:** `member`
- **Retry:** `idempotent`
- **Hint:** destructive operation.

#### Input schema

```json
{
  "type": "object",
  "additionalProperties": false,
  "required": ["action_id", "idempotency_key"],
  "properties": {
    "action_id": {
      "type": "string",
      "minLength": 1
    },
    "idempotency_key": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    }
  }
}
```

### `inbox.reject_action`

Reject a pending refund action without executing a Shopify refund. Requires action_id and idempotency_key.

- **Role:** `member`
- **Retry:** `idempotent`

#### Input schema

```json
{
  "type": "object",
  "additionalProperties": false,
  "required": ["action_id", "idempotency_key"],
  "properties": {
    "action_id": {
      "type": "string",
      "minLength": 1
    },
    "idempotency_key": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    }
  }
}
```
