# Intercom MCP Server

Intercom MCP server for Help Center content management and CS workflow automation.

## Version

**v0.9.0** - Added `send_outbound_email` — start a NEW outbound email to a contact by email address (resolves/creates the contact, retries on the post-create 404 propagation delay). Complements `reply_conversation`, which only replies within an existing conversation.

**v0.8.4** - Internal cleanup: removed the unused `zod` dependency; extracted shared `ok()` (tool-result wrapper) and `clamp()` (pagination) helpers to cut ~140 lines of repetition; dropped four decorative pass-through interfaces. No tool, schema, or output changes.

**v0.8.3** - `get_conversation` no longer drops message-bearing `open` parts (customer email replies that reopen a closed conversation) or `assignment` parts (the first admin reply); also surfaces per-part `attachments` and lifts ticket-form content into explicit `ticket.title` / `ticket.description` — fixes triage missing the latest customer message or screenshots

**v0.8.2** - `get_conversation` keeps triage essentials (source/delivered_as, ticket-form attributes, contacts, per-part `from_quick_reply` flag) instead of stripping them — needed to tell "typed a reply" from "tapped a quick-reply button"

**v0.8.1** - Fix: `search_conversations` accepts the `query` even when the MCP client serializes the object as a JSON string (Intercom rejected the stringified form)

**v0.8.0** - Added `search_conversations` & `get_conversation`; slimmed all action-tool responses (reply/close/note/article/ticket) to return only confirmation fields instead of the full Intercom object — prevents flooding the MCP client's context window

**v0.7.0** - Added delete_article, create_collection, list_admins; optimized search_articles response

## Features

### Articles
- ✅ `get_article` - Get a single article by ID
- ✅ `list_articles` - List articles with pagination
- ✅ `search_articles` - Search articles by keywords with highlighting support
- ✅ `create_article` - Create new articles with multilingual content
- ✅ `update_article` - Update existing articles with partial updates
- ✅ `delete_article` - Delete an article permanently

### Collections
- ✅ `list_collections` - List all Help Center collections
- ✅ `get_collection` - Get a single collection by ID
- ✅ `update_collection` - Update collection info and translations
- ✅ `delete_collection` - Delete a collection (permanent)
- ✅ `create_collection` - Create new Help Center collections

### Admin
- ✅ `list_admins` - List workspace admins (useful for finding valid author_id)

### Conversations
- ✅ `search_conversations` - Search conversations (slim list: id/state/contact/timestamps, no parts)
- ✅ `get_conversation` - Get one conversation with message history (slim: keeps comment/note/quick_reply plus any message-bearing open/assignment part + attachments; pure system events filtered out)

### CS Workflow
- ✅ `reply_conversation` - Reply to a conversation as an admin (returns slim confirmation)
- ✅ `send_outbound_email` - Start a NEW outbound email to a contact by email address (resolves/creates contact, returns new conversation_id)
- ✅ `add_conversation_note` - Add an internal note to a conversation
- ✅ `close_conversation` - Close a conversation
- ✅ `update_ticket_state` - Update a ticket's state

## Installation

1. Clone the repository:
```bash
git clone https://github.com/kaosensei/intercom-mcp.git
cd intercom-mcp
```

2. Install dependencies:
```bash
npm install
```

3. Build the project:
```bash
npm run build
```

## Configuration

### Get Intercom Access Token

1. Go to Intercom Settings → Developers → Developer Hub
2. Create a new app or use existing one
3. Get an Access Token with **Articles** and **Conversations** read and write permissions

### Environment Variables

| Variable | Required | Description |
|---|---|---|
| `INTERCOM_ACCESS_TOKEN` | ✅ Always | Your Intercom API access token |
| `INTERCOM_ADMIN_ID` | ✅ For CS tools | Admin ID used for `reply_conversation`, `add_conversation_note` and `send_outbound_email` when `admin_id` parameter is not provided |

### Configure with Claude Code (Recommended)

If you're using Claude Code CLI, you can easily add the MCP server:

```bash
claude mcp add --transport stdio intercom-mcp \
  --env INTERCOM_ACCESS_TOKEN=<your_token> \
  --env INTERCOM_ADMIN_ID=<your_admin_id> \
  -- node /ABSOLUTE/PATH/TO/intercom-mcp/dist/index.js
```

Replace:
- `<your_token>` with your Intercom Access Token
- `/ABSOLUTE/PATH/TO/` with your actual project path

To verify it's configured:
```bash
claude mcp list
```

### Configure Claude Desktop Manually

Alternatively, edit your Claude Desktop config file:

**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`

Add this configuration:

```json
{
  "mcpServers": {
    "intercom-mcp": {
      "command": "node",
      "args": [
        "/ABSOLUTE/PATH/TO/intercom-mcp/dist/index.js"
      ],
      "env": {
        "INTERCOM_ACCESS_TOKEN": "your_intercom_access_token_here",
        "INTERCOM_ADMIN_ID": "your_admin_id_here"
      }
    }
  }
}
```

**Important**:
- Replace `/ABSOLUTE/PATH/TO/intercom-mcp` with your actual project path
- Replace `your_intercom_access_token_here` with your actual token
- Replace `your_admin_id_here` with your Intercom admin ID (required for CS tools)

### Restart Claude Desktop

Completely quit Claude Desktop and restart it.

## Usage

Once configured, you can use these commands in Claude Desktop:

### List Articles
```
List Intercom articles
```

or

```
Show me the first 20 Intercom articles
```

### Get Article Details
```
Get Intercom article with ID 9876543
```

### Search Articles
```
Search for Intercom articles about "subscription"
```

or

```
Search published articles containing "播客" with highlighted matches
```

or

```
Find articles with keyword "訂閱" in Chinese
```

### Create Article
```
Create a new Intercom article titled "Getting Started Guide" with content "Welcome to our platform" by author ID 123456, save as draft
```

### Update Article
```
Update article 9876543 and change its state to published
```

### List Collections
```
List all Intercom Help Center collections
```

### Get Collection
```
Get collection with ID 14608214
```

### Update Collection
```
Update collection 14608214 and add Japanese translation
```

### Delete Collection
```
Delete collection 16036040
```

## Use Case: Translation Management

One of the key features of v0.4.0 is the ability to manage multilingual collections efficiently.

### Add Missing Translations

You can easily add translations to collections that are missing certain languages:

```
Update collection 14608214 and add the missing Japanese translation: name "アカウント管理", description "アカウント設定を管理する"
```

### Bulk Translation Updates

Check which collections are missing translations:

```
List all collections and show me which ones are missing Japanese translations
```

Then update them one by one or create a plan to update multiple collections.

### Verify Translations

After updating, verify the changes:

```
Get collection 14608214 and show me all available translations
```

## Tools Reference

### `get_article`

Get a single article by ID.

**Parameters:**
- `id` (string, required): Article ID

**Example:**
```json
{
  "id": "9876543"
}
```

### `list_articles`

List articles with pagination.

**Parameters:**
- `page` (number, optional): Page number (default: 1)
- `per_page` (number, optional): Articles per page (default: 10, max: 50)

**Example:**
```json
{
  "page": 1,
  "per_page": 20
}
```

### `search_articles`

Search for articles using keywords. Supports full-text search across article content with multilingual support (English, Chinese, Japanese, etc.).

**Parameters:**
- `phrase` (string, optional): Search keywords/phrase to find in articles
- `state` (string, optional): Filter by article state - "published", "draft", or "all" (default: "all")
- `help_center_id` (string, optional): Filter by specific Help Center ID

**Example (Search by keyword):**
```json
{
  "phrase": "subscription"
}
```

**Example (List all drafts, no keyword needed):**
```json
{
  "state": "draft"
}
```

**Example (Chinese keyword search):**
```json
{
  "phrase": "訂閱制",
  "state": "all"
}
```

**Response includes:**
- `total_count`: Total number of matching articles
- `articles`: Array of summary fields per article (id, title, description, state, url, author_id, created_at, updated_at, parent_id, parent_type)

Use `get_article` to fetch the full content of a specific article.

**Use Cases:**
- Find all articles about a specific topic
- Search for Chinese/Japanese content in multilingual help centers
- Locate articles that need updating
- Discover related content for cross-linking

### `create_article`

Create a new article with multilingual support.

**Parameters:**
- `title` (string, required): Article title
- `body` (string, required): Article content in HTML format
- `author_id` (number, required): Author ID (must be a valid Intercom team member)
- `description` (string, optional): Article description
- `state` (string, optional): "draft" or "published" (default: "draft")
- `parent_id` (string, optional): Collection or section ID
- `parent_type` (string, optional): "collection" (default)
- `translated_content` (object, optional): Multilingual content

**Example (Simple):**
```json
{
  "title": "Getting Started Guide",
  "body": "<p>Welcome to our platform</p>",
  "author_id": 123456,
  "state": "draft"
}
```

**Example (Multilingual):**
```json
{
  "title": "Getting Started Guide",
  "body": "<p>Welcome to our platform</p>",
  "author_id": 123456,
  "state": "published",
  "translated_content": {
    "zh-TW": {
      "title": "入門指南",
      "body": "<p>歡迎使用我們的平台</p>",
      "author_id": 123456,
      "state": "published"
    },
    "ja": {
      "title": "スタートガイド",
      "body": "<p>プラットフォームへようこそ</p>",
      "author_id": 123456,
      "state": "published"
    }
  }
}
```

### `update_article`

Update an existing article. Only provided fields will be updated.

**Parameters:**
- `id` (string, required): Article ID
- `title` (string, optional): Updated title
- `body` (string, optional): Updated content
- `description` (string, optional): Updated description
- `state` (string, optional): "draft" or "published"
- `author_id` (number, optional): Updated author ID
- `translated_content` (object, optional): Updated translations

**Example (Change state):**
```json
{
  "id": "9876543",
  "state": "published"
}
```

**Example (Update content):**
```json
{
  "id": "9876543",
  "title": "Updated Title",
  "body": "<p>Updated content</p>"
}
```

**Example (Add translation):**
```json
{
  "id": "9876543",
  "translated_content": {
    "zh-TW": {
      "title": "更新的標題",
      "body": "<p>更新的內容</p>"
    }
  }
}
```

### `list_collections`

List all Help Center collections (top-level categories).

**Parameters:**
- `page` (number, optional): Page number (default: 1)
- `per_page` (number, optional): Collections per page (default: 50, max: 150)

**Example:**
```json
{
  "page": 1,
  "per_page": 50
}
```

### `get_collection`

Get a single collection by ID.

**Parameters:**
- `id` (string, required): Collection ID

**Example:**
```json
{
  "id": "14608214"
}
```

### `update_collection`

Update an existing collection. Only provided fields will be updated. Perfect for adding missing translations!

**Parameters:**
- `id` (string, required): Collection ID
- `name` (string, optional): Updated collection name (updates default language)
- `description` (string, optional): Updated description (updates default language)
- `parent_id` (string, optional): Parent collection ID (null for top-level)
- `translated_content` (object, optional): Updated translations

**Example (Update name and description):**
```json
{
  "id": "14608214",
  "name": "Account Management",
  "description": "Manage your account settings"
}
```

**Example (Add missing Japanese translation):**
```json
{
  "id": "14608214",
  "translated_content": {
    "ja": {
      "name": "アカウント管理",
      "description": "アカウント設定を管理"
    }
  }
}
```

**Example (Update multiple language translations):**
```json
{
  "id": "14608214",
  "translated_content": {
    "ja": {
      "name": "アカウント管理",
      "description": "アカウント設定を管理する"
    },
    "id": {
      "name": "Manajemen Akun",
      "description": "Kelola pengaturan akun Anda"
    }
  }
}
```

### `delete_collection`

Delete a collection permanently. **WARNING: This action cannot be undone!**

**Parameters:**
- `id` (string, required): Collection ID to delete

**Example:**
```json
{
  "id": "16036040"
}
```

**⚠️ Important Notes:**
- Deleted collections cannot be restored
- All content within the collection may be affected
- Always backup important data before deletion

### `delete_article`

Delete an article permanently. **WARNING: This action cannot be undone!**

**Parameters:**
- `id` (string, required): Article ID to delete

**Example:**
```json
{
  "id": "9876543"
}
```

### `create_collection`

Create a new Help Center collection.

**Parameters:**
- `name` (string, required): Collection name
- `description` (string, optional): Collection description
- `parent_id` (string, optional): Parent collection ID for nesting (omit for top-level)
- `translated_content` (object, optional): Multilingual content by locale code

**Example (Simple):**
```json
{
  "name": "Getting Started"
}
```

**Example (With translation):**
```json
{
  "name": "Getting Started",
  "translated_content": {
    "zh-TW": {
      "name": "入門指南",
      "description": "開始使用我們的平台"
    }
  }
}
```

### `list_admins`

List all workspace admins. Useful for finding valid `author_id` values when creating or updating articles.

**Parameters:** None

**Response includes:**
- `id`: Admin ID (use this as `author_id`)
- `name`: Display name
- `email`: Email address
- `has_inbox_seat`: Whether the admin has an inbox seat

### `search_conversations`

Search conversations with an Intercom query object. Returns a **slim** list (no conversation parts) — call `get_conversation` for full content.

**Parameters:**
- `query` (object, required): Intercom search query object, e.g. `{"operator":"AND","value":[{"field":"state","operator":"=","value":"open"}]}`. Add `{"field":"admin_assignee_id","operator":"=","value":<id>}` to filter by assignee, or `source.author.email` by contact. Also accepts the object serialized as a JSON string.
- `per_page` (number, optional): Results per page (default: 20, max: 50)
- `starting_after` (string, optional): Pagination cursor from a previous response's `next`

**Example (all open conversations):**
```json
{
  "query": {"operator":"AND","value":[{"field":"state","operator":"=","value":"open"}]},
  "per_page": 50
}
```

**Response includes:**
- `total_count`: Total matching conversations
- `next`: Pagination cursor (present if more pages)
- `conversations`: Array of slim items (id, state, open, title, subject, contact, admin_assignee_id, timestamps) — no parts

### `get_conversation`

Get a single conversation with message history, **slimmed** for triage: pure system-event parts filtered out, but every message-bearing part (including reopen email replies and the first admin reply) and its attachments kept.

**Parameters:**
- `id` (string, required): Conversation ID

**Example:**
```json
{
  "id": "12345678"
}
```

**Response includes:**
- Top level: `id`, `state`, `open`, `title`, timestamps, `waiting_since`
- `source`: incl. `delivered_as` (identifies the real asker), `subject`, `body`, `author`
- `ticket`: ticket-form essentials — `type`, `state`, and `title` / `description` (the real request, pulled out of the internal `_default_title_` / `_default_description_` keys; present only when the ticket has them)
- `ticket_attributes`: all ticket-form custom attributes with non-empty values
- `contacts`: contact references
- `total_parts` / `included_parts`: counts before / after filtering
- `parts`: `comment` / `note` / `quick_reply`, **plus** any `open` / `assignment` part that carries a real body or attachments (reopen email replies, the first admin reply). Each `{ part_type, body, author, from_quick_reply, attachments, created_at }` — `from_quick_reply` distinguishes a typed reply from a tapped quick-reply option; `attachments` (when present) lists `{ name, url, content_type }`

### `reply_conversation`

Reply to a conversation as an admin. The reply is visible to the customer. Returns a slim confirmation object (`id`, `state`, `last_part_id`, timestamps).

**Parameters:**
- `conversation_id` (string, required): The conversation ID to reply to
- `body` (string, required): The reply message body (supports HTML)
- `admin_id` (string, optional): Admin ID to reply as (defaults to `INTERCOM_ADMIN_ID` env var)

**Example:**
```json
{
  "conversation_id": "12345678",
  "body": "<p>Thank you for reaching out. We'll look into this right away.</p>"
}
```

### `send_outbound_email`

Start a **new** outbound email to a contact identified by email address — not a reply to an existing conversation. The tool resolves the email to an Intercom contact (creating a `lead` if none exists), then sends via the Messages API. Because a freshly-created contact can briefly return 404 on send, the call retries with backoff. Returns the new `conversation_id` so you can note/track it.

Use `reply_conversation` instead when responding inside an existing conversation.

**Parameters:**
- `email` (string, required): Recipient's email address
- `subject` (string, required): Email subject
- `body` (string, required): Email body (supports HTML)
- `admin_id` (string, optional): Admin ID to send as (defaults to `INTERCOM_ADMIN_ID` env var). Determines the sender name/address the recipient sees.
- `template` (string, optional): `plain` or `personal` (defaults to `personal` — a 1:1 personal-email look)

**Example:**
```json
{
  "email": "member@example.com",
  "subject": "About your subscription",
  "body": "<p>Hello, ...</p>",
  "admin_id": "8530422"
}
```

**Note:** the Messages API requires `to.id` to be an Intercom contact id, never a raw email — this tool handles the lookup/creation for you.

### `add_conversation_note`

Add an internal note to a conversation. Notes are only visible to team members, not customers.

**Parameters:**
- `conversation_id` (string, required): The conversation ID to add a note to
- `body` (string, required): The note content (supports HTML)
- `admin_id` (string, optional): Admin ID adding the note (defaults to `INTERCOM_ADMIN_ID` env var)

**Example:**
```json
{
  "conversation_id": "12345678",
  "body": "<p>Customer has been refunded. Follow up in 3 days.</p>"
}
```

### `close_conversation`

Close a conversation.

**Parameters:**
- `conversation_id` (string, required): The conversation ID to close

**Example:**
```json
{
  "conversation_id": "12345678"
}
```

### `update_ticket_state`

Update the state of a ticket.

**Parameters:**
- `ticket_id` (string, required): The ticket ID to update
- `state` (string, required): The new ticket state — one of `in_progress`, `waiting_on_customer`, `resolved`

**Example:**
```json
{
  "ticket_id": "87654321",
  "state": "resolved"
}
```

## Development

### Build
```bash
npm run build
```

### Watch mode
```bash
npm run watch
```

## Troubleshooting

### Claude Desktop doesn't show the tools

1. Check config file path is correct
2. Verify JSON format (no trailing commas)
3. Completely restart Claude Desktop
4. Check absolute path to `dist/index.js`

### API errors

1. Verify your Access Token is correct
2. Ensure token has Articles and Conversations read/write permissions
3. Check Intercom API status

### Build errors

1. Ensure TypeScript version >= 5.0
2. Delete `node_modules` and `dist`, then:
```bash
npm install && npm run build
```

## Project Structure

```
intercom-mcp/
├── package.json           # Project configuration
├── tsconfig.json          # TypeScript configuration
├── src/
│   └── index.ts           # Main server code
├── dist/                  # Compiled output
└── README.md             # This file
```

## Roadmap

### Completed
- ✅ Get Article (v0.1.0)
- ✅ List Articles (v0.1.0)
- ✅ Create Article (v0.2.0)
- ✅ Update Article (v0.2.0)
- ✅ Multilingual support for Articles (v0.2.0)
- ✅ List Collections (v0.3.1)
- ✅ Get Collection (v0.3.1)
- ✅ Update Collection (v0.4.0)
- ✅ Delete Collection (v0.4.0)
- ✅ Multilingual support for Collections (v0.4.0)
- ✅ Search Articles with keyword matching and highlighting (v0.5.0)
- ✅ Reply to conversations (v0.6.0)
- ✅ Add internal notes to conversations (v0.6.0)
- ✅ Close conversations (v0.6.0)
- ✅ Update ticket state (v0.6.0)
- ✅ Delete Article (v0.7.0)
- ✅ Create Collection (v0.7.0)
- ✅ List Admins (v0.7.0)
- ✅ Optimized search_articles response (v0.7.0)
- ✅ Search conversations — slim list (v0.8.0)
- ✅ Get conversation — slim, triage-aware (system events filtered, `from_quick_reply` flag) (v0.8.0)
- ✅ Slim action-tool responses to protect client context (v0.8.0)
- ✅ Get conversation — keep reopen/assignment message parts + attachments, surface ticket title/description (v0.8.3)

### Planned
- 🔜 Batch operations
- 🔜 Better error handling
- 🔜 Modular file structure

## Resources

- [Intercom Articles API](https://developers.intercom.com/docs/references/rest-api/api.intercom.io/articles)
- [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
- [MCP Documentation](https://modelcontextprotocol.io/)

## License

MIT
