# MCP server

> Run your support inbox from Claude Code, Cursor, Codex, Windsurf, VS Code, Gemini CLI, Zed or any MCP client. Read and write.

Source: https://conciergesdk.com/docs/mcp/

Concierge runs a remote MCP server, so your coding agent can read **and write** your inbox. A reply sent from your editor reaches the customer's app live and by push, exactly like a reply from the dashboard. "Triage my support, fix the crash Ana reported, and reply to her" is one prompt.

| Environment | URL |
|---|---|
| Hosted | `https://mcp.conciergesdk.com/mcp` |
| Local dev | `http://localhost:4100/mcp` |

It speaks MCP over **Streamable HTTP**, so it works with any client that does: Claude Code, Cursor, Codex, Windsurf, VS Code with GitHub Copilot, Gemini CLI, Zed and others. Config for each is below.

## Sign in with OAuth (no key)

Clients that support MCP OAuth (Claude Code, Cursor, Codex) need nothing but the URL:

```sh
claude mcp add --transport http concierge https://mcp.conciergesdk.com/mcp
```

The first call opens your browser: sign in to the dashboard, review what the agent can do, and click **Allow**. The agent registers itself, signs in with PKCE and keeps a refresh token, so you only do this once. It acts as you in the organization that was active when you clicked Allow, and its replies carry your name.

Anyone can register an app under any name, so the consent screen marks every agent **Unverified app** and shows where access will be sent. Allow only if that address is your own computer (localhost or 127.0.0.1) or a site you started the connection from. Redirects must be https or a loopback http address. Clients that redirect to a custom scheme (for example `cursor://`) work only when the server operator allows that scheme; otherwise use an API key below. An agent that asks for `concierge:read` without `concierge:write` gets read only access.

## Or create an API key

Scripts, CI and clients without OAuth sign in with a staff API key: `ck_` plus 40 characters. It's shown once, and only a hash is stored.

- **Dashboard:** create a key in your settings (it calls `POST /v1/api-keys { name }`). List keys with `GET /v1/api-keys` (prefix only) and revoke with `DELETE /v1/api-keys/:id`.
- **CLI, local dev:**

```sh
pnpm --filter mcp key:create
# or, explicitly:
pnpm --filter mcp key:create -- --email you@example.com --org org_... --name "Claude Code"
```

Every flag is optional; it defaults to the seeded demo user and their first organization. `--quiet` prints only the key.

A key belongs to one organization and acts as the person who created it, so replies are signed with their name. It stops working the moment it's revoked or that person leaves the org. Anyone can revoke their own keys; owners and admins can revoke any key in the org.

## Connect your agent

**Claude Code** (with a key; drop the header to use OAuth instead):

```sh
claude mcp add --transport http concierge https://mcp.conciergesdk.com/mcp --header "Authorization: Bearer ck_..."
```

For local dev, use `http://localhost:4100/mcp` instead (start it with `pnpm dev:mcp`).

**Cursor** (`.cursor/mcp.json`) and other clients that take JSON config:

```json
{
  "mcpServers": {
    "concierge": {
      "url": "https://mcp.conciergesdk.com/mcp",
      "headers": { "Authorization": "Bearer ck_..." }
    }
  }
}
```

**Codex** (`~/.codex/config.toml`):

```toml
[mcp_servers.concierge]
url = "https://mcp.conciergesdk.com/mcp"
http_headers = { Authorization = "Bearer ck_..." }
```

**Windsurf** (`~/.codeium/windsurf/mcp_config.json`):

```json
{
  "mcpServers": {
    "concierge": {
      "serverUrl": "https://mcp.conciergesdk.com/mcp",
      "headers": { "Authorization": "Bearer ck_..." }
    }
  }
}
```

**VS Code with GitHub Copilot** (`.vscode/mcp.json`, agent mode):

```json
{
  "servers": {
    "concierge": {
      "type": "http",
      "url": "https://mcp.conciergesdk.com/mcp",
      "headers": { "Authorization": "Bearer ck_..." }
    }
  }
}
```

**Gemini CLI** (`~/.gemini/settings.json`):

```json
{
  "mcpServers": {
    "concierge": {
      "httpUrl": "https://mcp.conciergesdk.com/mcp",
      "headers": { "Authorization": "Bearer ck_..." }
    }
  }
}
```

**Zed** (`settings.json`):

```json
{
  "context_servers": {
    "concierge": {
      "url": "https://mcp.conciergesdk.com/mcp",
      "headers": { "Authorization": "Bearer ck_..." }
    }
  }
}
```

**Any other MCP client:** point it at `https://mcp.conciergesdk.com/mcp` as a Streamable HTTP (sometimes labeled "HTTP") server with the header `Authorization: Bearer ck_...`. Clients that only launch local stdio servers can bridge with `npx mcp-remote https://mcp.conciergesdk.com/mcp --header "Authorization: Bearer ck_..."`.

Then ask things like "triage my support inbox".

### Set up the dashboard from your agent

Every settings page in the dashboard has a **Set up with your agent** button. It copies a short prompt for that area: connect the server if needed, read the current settings, look up the facts in your repo (prices from your StoreKit configuration, FAQ from your README, colors from your asset catalog, bundle ids from your build files), show you the planned changes, then write them with the tools below.

## Tools

Every tool returns a short text summary plus structured content. IDs from another organization behave exactly like missing IDs.

| Tool | What it does |
|---|---|
| `list_apps` | Your apps with app keys, connected state and open conversation counts. |
| `list_conversations` | Filter by `status` (open, snoozed, closed, all; default open), `appId`, `priority`, `query` (name, email, user id, message text) and `limit`. |
| `get_conversation` | Messages (paged with `before`), the customer with metadata and breadcrumbs, devices and status. |
| `reply` | Replies as you. The customer gets it live and by push, and the AI pauses on that thread. Optional `status`. Idempotent on `clientId`. |
| `send_card` | A `review` card (store link) or an `offer` card (paywall id), with optional text. |
| `update_conversation` | Set `status`, `priority` or `aiState`. |
| `search_customers` | By name, email, user id, customer id or a metadata value. |
| `get_customer` | Profile, devices and conversation summary. |
| `get_app_stats` | Open, snoozed, closed, unread, AI resolved, median first response, customers and 7 day volume. |
| `get_setup_instructions` | SDK install steps for `ios`, `android` or `react-native`, with your app key filled in. |
| `verify_install` | Whether the first real message arrived, devices per platform, SDK versions, push readiness, whether `onPaywallCard` and `onOfferCard` are set (SDK 0.1.1 or later) and where review cards link. |
| `list_knowledge` | The AI agent's knowledge base. |
| `upsert_knowledge` | Adds or updates a knowledge article, matched by id, then by title. Takes the structured fields (`feature`, `screenPath`, `steps`, `planRequired`, `knownIssues`, `relatedScreens`, `deepLinkId`) and `draft: true` to leave it for review. |
| `list_screens` | The feature map: screen ids and titles your app registered with `registerScreen` (never deep links). |
| `list_knowledge_gaps` | Questions the AI couldn't answer, most asked first. |
| `get_app_settings` | Every settings area of one app in one call: app, AI agent, appearance, identity, security and allowed websites, kill switch, knowledge autopilot, store integrations, review replies, money, paywall and offer limits. Optional `sections`. Secrets are never returned, only whether each is set. |
| `update_app_settings` | Name, icon, store listing (`appStoreId`, `playPackage`, which review cards link to), `ai`, `appearance`, `identity`, `security` (`iosAppId`, `androidPackage`, `allowedOrigins`), `sdk` and `knowledge`. Send only what changes. Identity, security, the kill switch, the autopilot and the AI's model, prompt and tools need an owner or admin. |
| `list_offers` | The offer catalog with card copy, what each platform gets and the AI policy. |
| `save_offer` | Creates an offer, or changes one by `offerId` (`status: draft` saves it without making it sendable, `active` activates it, `archived` retires it). Offers the AI sends on its own or that grant free access need an owner or admin. |
| `update_offer_limits` | Org wide offer caps and comp policy. Owner or admin. |
| `update_store_integrations` | App Store, Google Play and RevenueCat ids and keys. Keys are write only: omit to keep, `null` to clear. Owner or admin. |
| `update_review_settings` | AI drafts, auto send, reply instructions and the App Store Connect key (write only). Owner or admin. |
| `update_money_settings` | Refund desk, locked purchase resolver, billing guard and win-back, by the fields sent. Owner or admin. |
| `update_paywall_settings` | Cancel flow, Apple and Google retention options and paywall help, by the fields sent. Owner or admin. |
| `import_conversations` | Imports past conversations from your previous support tool as history (`externalId`, customer `userId`, `status`, `metadata`, `messages` with `role`, `body` and `createdAt`). Idempotent by `externalId`, at most 100 conversations and 2,000 messages a call. Never triggers AI replies, push or emails. Owner or admin, Pro. See [Switch](https://conciergesdk.com/docs/migrate.md#conversation-history). |
| `export_customer_data` | Everything stored for one end user (by `customerId`, or `appId` and `userId`) as JSON, for a GDPR or CCPA access request. Owner or admin. |
| `delete_customer_data` | Permanently deletes all data for one end user, photos included. Owner or admin, never with a read only token, and `confirm` must repeat the `customerId` (or `userId`). |
| `list_incidents` | [Version incidents](https://conciergesdk.com/docs/incidents.md): spikes of similar tickets from one app version, with counts, first and last seen, and the auto reply. |
| `get_incident` | One incident with its newest tickets, OS and build. |
| `set_incident_auto_reply` | The one reply affected users get (open conversations now, new matching tickets as they arrive), or `null` to stop. Pro. |
| `resolve_incident` | Closes an incident; with `fixedInVersion` and `notify` every affected conversation hears it is fixed. `dismiss` for a false alarm. |

## Prompts

- **`triage`** (`/mcp__concierge__triage` in Claude Code): open conversations grouped by urgency, patterns across them, and drafted replies. Nothing is sent without your approval.
- **`build-knowledge`** (`/mcp__concierge__build-knowledge app_...` in your app's repo, optional focus): reads your screens, settings, paywall, flags, errors and strings, proposes missing `registerScreen` calls, shows the articles it plans, then writes them as drafts. See [Knowledge autopilot](https://conciergesdk.com/docs/knowledge.md#build-it-from-your-codebase-mcp).
- **`fix-and-reply`** (`/mcp__concierge__fix-and-reply cnv_...`): finds the bug in your current repo from the report, breadcrumbs and device info, fixes and tests it, then replies and sets the status.

## Resources

`concierge://conversation/{id}` is a Markdown transcript of a conversation. The listing shows the 50 most recent conversations, and ids autocomplete.

## Security notes

- Requests from a browser `Origin` are refused unless it's localhost or on your allow list. CLI agents send no origin and are fine.
- Every request checks the key or token and the person's org membership.
- Anything a customer wrote (messages, names, metadata, breadcrumbs, tool results) reaches the agent inside `<customer_message>` or `<customer_data>` blocks, escaped, with a standing rule that it is data and never instructions. `grant_promo`, `send_card`, `upsert_knowledge`, `call_client_tool` and the settings writers (`update_app_settings`, `save_offer` and the other `update_` tools) are marked destructive, so clients ask before running them.
- Store keys, private keys, service account JSON and the identity secret are write only over MCP: no tool returns them.
- `grant_promo` follows your workspace comp policy: owners and admins only by default, at most 31 days, 20 grants a day and 2 per customer per 30 days, and an optional entitlement list. Change it under Offers, Limits.
- Each person gets 120 MCP requests a minute, and at most 3 `call_client_tool` waits at once.
- OAuth access tokens are short lived JWTs (one hour) issued only for this MCP server; refresh tokens last 30 days.
