# Directory Structure
```
├── .env.example
├── .gitignore
├── package.json
├── README.md
├── src
│ ├── api
│ │ └── client.ts
│ ├── index.ts
│ ├── tools
│ │ └── conversations.ts
│ └── types
│ └── intercom.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
```
# Intercom API credentials
INTERCOM_API_KEY=your_api_key_here
# Optional configuration
LOG_LEVEL=info
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Dependencies
node_modules/
yarn.lock
package-lock.json
# Build output
dist/
build/
# Environment variables
.env
.env.local
# IDE files
.vscode/
.idea/
*.swp
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# OS files
.DS_Store
Thumbs.db
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# MCP Intercom Server
A Model Context Protocol (MCP) server that provides access to Intercom conversations and chats. This server allows LLMs to query and analyze your Intercom conversations with various filtering options.
## Features
- Query Intercom conversations with filtering options:
- Date range (start and end dates)
- Customer ID
- Conversation state
- Secure access using your Intercom API key
- Rich conversation data including:
- Basic conversation details
- Contact information
- Statistics (responses, reopens)
- State and priority information
## Installation
1. Clone the repository:
```bash
git clone https://github.com/fabian1710/mcp-intercom.git
cd mcp-intercom
```
2. Install dependencies:
```bash
npm install
```
3. Set up your environment:
```bash
cp .env.example .env
```
4. Add your Intercom API key to `.env`:
```
INTERCOM_API_KEY=your_api_key_here
```
5. Build the server:
```bash
npm run build
```
## Usage
### Running the Server
Start the server:
```bash
npm start
```
### Using with Claude for Desktop
1. Add the server to your Claude for Desktop configuration (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS or `%AppData%\Claude\claude_desktop_config.json` on Windows):
```json
{
"mcpServers": {
"intercom": {
"command": "node",
"args": ["/path/to/mcp-intercom/dist/index.js"],
"env": {
"INTERCOM_API_KEY": "your_api_key_here"
}
}
}
}
```
2. Restart Claude for Desktop
### Available Tools
#### search-conversations
Searches Intercom conversations with optional filters.
Parameters:
- `createdAt` (optional): Object with `operator` (e.g., ">", "<", "=") and `value` (UNIX timestamp) for filtering by creation date.
- `updatedAt` (optional): Object with `operator` (e.g., ">", "<", "=") and `value` (UNIX timestamp) for filtering by update date.
- `sourceType` (optional): Source type of the conversation (e.g., "email", "chat").
- `state` (optional): Conversation state to filter by (e.g., "open", "closed").
- `open` (optional): Boolean to filter by open status.
- `read` (optional): Boolean to filter by read status.
Example queries:
- "Search for all conversations created after January 1, 2024"
- "Find conversations updated before last week"
- "List all open email conversations"
- "Get all unread conversations"
## Security
- The server requires an Intercom API key to function
- API key should be stored securely in environment variables
- The server only provides read access to conversations
- All API requests are made with proper authentication
## Development
1. Start development mode with auto-recompilation:
```bash
npm run dev
```
2. Run linting:
```bash
npm run lint
```
## Contributing
1. Fork the repository
2. Create a new branch for your feature
3. Make your changes
4. Submit a pull request
## License
MIT
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "mcp-intercom",
"version": "1.0.0",
"description": "MCP server for Intercom chat integration",
"type": "module",
"main": "dist/index.js",
"bin": {
"mcp-intercom": "./dist/index.js"
},
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "tsc -w",
"lint": "eslint src --ext .ts",
"test": "jest"
},
"keywords": [
"mcp",
"intercom",
"chat"
],
"author": "",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.4",
"axios": "^1.7.9",
"dotenv": "^16.3.1",
"node-fetch": "^3.3.2",
"zod": "^3.22.4"
},
"devDependencies": {
"@types/node": "^20.11.5",
"@typescript-eslint/eslint-plugin": "^6.19.0",
"@typescript-eslint/parser": "^6.19.0",
"eslint": "^8.56.0",
"typescript": "^5.3.3"
}
}
```
--------------------------------------------------------------------------------
/src/types/intercom.ts:
--------------------------------------------------------------------------------
```typescript
export interface IntercomConversation {
id: string;
created_at: number;
updated_at: number;
waiting_since: number;
snoozed_until?: number;
source: {
type: string;
id: string;
delivered_as: string;
};
contacts: {
type: string;
id: string;
name: string;
}[];
statistics: {
time_to_assignment?: number;
time_to_first_response?: number;
time_to_last_close?: number;
median_time_to_response?: number;
first_contact_reply_at?: number;
first_assignment_at?: number;
first_admin_reply_at?: number;
first_close_at?: number;
last_assignment_at?: number;
last_assignment_admin_reply_at?: number;
last_contact_reply_at?: number;
last_admin_reply_at?: number;
last_close_at?: number;
reopens: number;
responses: number;
};
state: string;
read: boolean;
priority: string;
}
```
--------------------------------------------------------------------------------
/src/api/client.ts:
--------------------------------------------------------------------------------
```typescript
import dotenv from "dotenv";
import { IntercomConversation } from "../types/intercom.js";
import axios from "axios";
dotenv.config();
const INTERCOM_API_BASE = "https://api.intercom.io";
export class IntercomClient {
private apiKey: string;
constructor() {
const apiKey = process.env.INTERCOM_API_KEY;
if (!apiKey) {
throw new Error("INTERCOM_API_KEY environment variable is required");
}
this.apiKey = apiKey;
}
private async request<T>(
path: string,
params: Record<string, string> = {}
): Promise<T> {
const url = new URL(path, INTERCOM_API_BASE);
Object.entries(params).forEach(([key, value]) => {
url.searchParams.append(key, value);
});
const response = await axios.get(url.toString(), {
headers: {
Authorization: `Bearer ${this.apiKey}`,
Accept: "application/json",
},
});
return response.data as T;
}
async searchConversations(
filters: {
createdAt?: { operator: string; value: number };
updatedAt?: { operator: string; value: number };
sourceType?: string;
state?: string;
open?: boolean;
read?: boolean;
// Add more filters as needed
} = {},
pagination: { perPage?: number; startingAfter?: string } = {}
) {
const query: any = {
operator: "AND",
value: [],
};
if (filters.createdAt) {
query.value.push({
field: "created_at",
operator: filters.createdAt.operator,
value: filters.createdAt.value.toString(),
});
}
if (filters.updatedAt) {
query.value.push({
field: "updated_at",
operator: filters.updatedAt.operator,
value: filters.updatedAt.value.toString(),
});
}
if (filters.sourceType) {
query.value.push({
field: "source.type",
operator: "=",
value: filters.sourceType,
});
}
if (filters.state) {
query.value.push({
field: "state",
operator: "=",
value: filters.state,
});
}
if (filters.open !== undefined) {
query.value.push({
field: "open",
operator: "=",
value: filters.open.toString(),
});
}
if (filters.read !== undefined) {
query.value.push({
field: "read",
operator: "=",
value: filters.read.toString(),
});
}
const body = {
query,
pagination: {
per_page: pagination.perPage || 20,
starting_after: pagination.startingAfter || null,
},
};
const response = await axios.post(
`${INTERCOM_API_BASE}/conversations/search`,
body,
{
headers: {
Authorization: `Bearer ${this.apiKey}`,
Accept: "application/json",
"Content-Type": "application/json",
"Intercom-Version": "2.11",
},
}
);
return response.data as { conversations: IntercomConversation[] };
}
}
```
--------------------------------------------------------------------------------
/src/tools/conversations.ts:
--------------------------------------------------------------------------------
```typescript
import { z } from "zod";
import { IntercomClient } from "../api/client.js";
export const GetConversationsSchema = z.object({
startDate: z.string().optional(),
endDate: z.string().optional(),
customer: z.string().optional(),
state: z.string().optional(),
});
export const SearchConversationsSchema = z.object({
createdAt: z
.object({
operator: z.enum(["=", "!=", ">", "<"]),
value: z.number(),
})
.optional(),
updatedAt: z
.object({
operator: z.enum(["=", "!=", ">", "<"]),
value: z.number(),
})
.optional(),
sourceType: z.string().optional(),
state: z.string().optional(),
open: z.boolean().optional(),
read: z.boolean().optional(),
// Add more filters as needed
});
export async function searchConversations(
args: z.infer<typeof SearchConversationsSchema>
) {
const client = new IntercomClient();
const params: Parameters<typeof client.searchConversations>[0] = {};
if (args.createdAt) {
params.createdAt = args.createdAt;
}
if (args.updatedAt) {
params.updatedAt = args.updatedAt;
}
if (args.sourceType) {
params.sourceType = args.sourceType;
}
if (args.state) {
params.state = args.state;
}
if (args.open) {
params.open = args.open;
}
if (args.read) {
params.read = args.read;
}
const { conversations } = await client.searchConversations(params);
return conversations.map((conv) => ({
id: conv.id,
created_at: new Date(conv.created_at * 1000).toISOString(),
updated_at: new Date(conv.updated_at * 1000).toISOString(),
state: conv.state,
priority: conv.priority,
contacts: conv.contacts.map((contact) => ({
name: contact.name,
id: contact.id,
})),
}));
}
export async function listConversationsFromLastWeek() {
// Calculate last week's date range
const client = new IntercomClient();
const now = new Date();
const lastWeekStart = new Date(now);
lastWeekStart.setDate(now.getDate() - 7);
lastWeekStart.setHours(0, 0, 0, 0);
const lastWeekEnd = new Date(lastWeekStart);
lastWeekEnd.setDate(lastWeekStart.getDate() + 6);
lastWeekEnd.setHours(23, 59, 59, 999);
// Convert to Unix timestamp (seconds)
const startTimestamp = Math.floor(lastWeekStart.getTime() / 1000);
const endTimestamp = Math.floor(lastWeekEnd.getTime() / 1000);
return client.searchConversations({
createdAt: {
operator: ">",
value: startTimestamp,
},
updatedAt: {
operator: "<",
value: endTimestamp,
},
});
}
// export async function getConversations(
// args: z.infer<typeof GetConversationsSchema>
// ) {
// const client = new IntercomClient();
// const params: Parameters<typeof client.getConversations>[0] = {};
// if (args.startDate) {
// params.startDate = new Date(args.startDate);
// }
// if (args.endDate) {
// params.endDate = new Date(args.endDate);
// }
// if (args.customer) {
// params.customer = args.customer;
// }
// if (args.state) {
// params.state = args.state;
// }
// const { conversations } = await client.getConversations(params);
// return conversations.map((conv) => ({
// id: conv.id,
// created_at: new Date(conv.created_at * 1000).toISOString(),
// updated_at: new Date(conv.updated_at * 1000).toISOString(),
// state: conv.state,
// priority: conv.priority,
// contacts: conv.contacts.map((contact) => ({
// name: contact.name,
// id: contact.id,
// })),
// statistics: {
// responses: conv.statistics.responses,
// reopens: conv.statistics.reopens,
// },
// }));
// }
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { IntercomClient } from "./api/client.js";
import { SearchConversationsSchema, listConversationsFromLastWeek } from "./tools/conversations.js";
import { z } from "zod";
const server = new Server(
{
name: "intercom",
version: "1.0.0",
},
{
capabilities: {
tools: {
"search-conversations": {
description:
"Search Intercom conversations with filters for created_at, updated_at, source type, state, open, and read status",
inputSchema: SearchConversationsSchema,
outputSchema: z.any(),
},
"list-conversations-from-last-week": {
description: "Fetch all conversations from the last week (last 7 days)",
inputSchema: z.object({}),
outputSchema: z.any(),
},
},
},
}
);
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "search-conversations",
description:
"Search Intercom conversations with filters for created_at, updated_at, source type, state, open, and read status",
inputSchema: {
type: "object",
properties: {
createdAt: {
type: "object",
properties: {
operator: {
type: "string",
description: 'Operator for created_at (e.g., ">", "<", "=")',
},
value: {
type: "integer",
description: "Timestamp value for created_at filter",
},
},
},
updatedAt: {
type: "object",
properties: {
operator: {
type: "string",
description: 'Operator for updated_at (e.g., ">", "<", "=")',
},
value: {
type: "integer",
description: "Timestamp value for updated_at filter",
},
},
},
sourceType: {
type: "string",
description:
'Source type of the conversation (e.g., "email", "chat")',
},
state: {
type: "string",
description:
'Conversation state to filter by (e.g., "open", "closed")',
},
open: {
type: "boolean",
description: "Filter by open status",
},
read: {
type: "boolean",
description: "Filter by read status",
},
},
},
},
{
name: "list-conversations-from-last-week",
description: "Fetch all conversations from the last week (last 7 days)",
inputSchema: {
type: "object",
properties: {},
},
},
],
};
});
// Handle tool execution
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === "search-conversations") {
try {
const validatedArgs = SearchConversationsSchema.parse(args);
const intercomClient = new IntercomClient();
const conversations = await intercomClient.searchConversations(
validatedArgs
);
return {
content: [
{
type: "text",
text: JSON.stringify(conversations, null, 2),
},
],
};
} catch (error) {
if (error instanceof Error) {
return {
content: [
{
type: "text",
text: `Error: ${error.message}`,
},
],
};
}
throw error;
}
}
if (name === "list-conversations-from-last-week") {
try {
const conversations = await listConversationsFromLastWeek();
return {
content: [
{
type: "text",
text: JSON.stringify(conversations, null, 2),
},
],
};
} catch (error) {
if (error instanceof Error) {
return {
content: [
{
type: "text",
text: `Error: ${error.message}`,
},
],
};
}
throw error;
}
}
throw new Error(`Unknown tool: ${name}`);
});
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Intercom MCP Server running on stdio");
}
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});
```