#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
// Intercom Article 型別
interface IntercomArticle {
id: string;
title: string;
description?: string;
body?: string;
author_id: number;
state: 'draft' | 'published';
created_at: number;
updated_at: number;
}
// List 回應型別
interface ListArticlesResponse {
type: 'list';
data: IntercomArticle[];
pages?: {
page: number;
per_page: number;
total_pages: number;
};
}
// 從環境變數取得 token
const INTERCOM_TOKEN = process.env.INTERCOM_ACCESS_TOKEN;
const INTERCOM_API_BASE = 'https://api.intercom.io';
/**
* 呼叫 Intercom API
*/
async function callIntercomAPI(
endpoint: string,
method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',
body?: any
): Promise<any> {
const options: RequestInit = {
method,
headers: {
'Authorization': `Bearer ${INTERCOM_TOKEN}`,
'Accept': 'application/json',
'Content-Type': 'application/json',
'Intercom-Version': '2.11'
}
};
if (body && (method === 'POST' || method === 'PUT')) {
options.body = JSON.stringify(body);
}
const response = await fetch(`${INTERCOM_API_BASE}${endpoint}`, options);
if (!response.ok) {
const error = await response.text();
throw new Error(`Intercom API error: ${response.status} - ${error}`);
}
// Handle 204 No Content response
if (response.status === 204) {
return { success: true };
}
return response.json();
}
/**
* 建立 MCP Server
*/
const server = new Server({
name: 'intercom-articles-mcp',
version: '0.2.0'
}, {
capabilities: {
tools: {}
}
});
/**
* 註冊工具列表
*/
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'get_article',
description: 'Get a single Intercom article by ID. Returns full article details including title, body, author, and state.',
inputSchema: {
type: 'object',
properties: {
id: {
type: 'string',
description: 'The article ID (e.g., "123456")'
}
},
required: ['id']
}
},
{
name: 'list_articles',
description: 'List Intercom articles with pagination. Returns a list of articles with basic information.',
inputSchema: {
type: 'object',
properties: {
page: {
type: 'number',
description: 'Page number (default: 1)',
default: 1
},
per_page: {
type: 'number',
description: 'Number of articles per page (default: 10, max: 50)',
default: 10
}
}
}
},
{
name: 'create_article',
description: 'Create a new Intercom Help Center article. Supports multilingual content and draft/published states.',
inputSchema: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Article title (required)'
},
body: {
type: 'string',
description: 'Article content in HTML format (required)'
},
author_id: {
type: 'number',
description: 'Author ID - must be a valid Intercom team member ID (required)'
},
description: {
type: 'string',
description: 'Article description (optional)'
},
state: {
type: 'string',
enum: ['draft', 'published'],
description: 'Article state (optional, default: draft)'
},
parent_id: {
type: 'string',
description: 'Parent ID - collection or section ID (optional)'
},
parent_type: {
type: 'string',
enum: ['collection'],
description: 'Parent type (optional, default: collection)'
},
translated_content: {
type: 'object',
description: 'Multilingual content. Key is locale code (e.g., "zh-TW"), value is translation object',
additionalProperties: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Translated title'
},
body: {
type: 'string',
description: 'Translated content in HTML'
},
description: {
type: 'string',
description: 'Translated description'
},
author_id: {
type: 'number',
description: 'Author ID for translation'
},
state: {
type: 'string',
enum: ['draft', 'published'],
description: 'Translation state'
}
},
required: ['title', 'body', 'author_id']
}
}
},
required: ['title', 'body', 'author_id']
}
},
{
name: 'update_article',
description: 'Update an existing Intercom Help Center article. Supports partial updates and multilingual content.',
inputSchema: {
type: 'object',
properties: {
id: {
type: 'string',
description: 'Article ID (required)'
},
title: {
type: 'string',
description: 'Updated article title (optional)'
},
body: {
type: 'string',
description: 'Updated article content in HTML format (optional)'
},
description: {
type: 'string',
description: 'Updated article description (optional)'
},
state: {
type: 'string',
enum: ['draft', 'published'],
description: 'Updated article state (optional)'
},
author_id: {
type: 'number',
description: 'Updated author ID (optional)'
},
translated_content: {
type: 'object',
description: 'Updated multilingual content. Only provided fields will be updated.',
additionalProperties: {
type: 'object',
properties: {
title: {
type: 'string',
description: 'Updated translated title'
},
body: {
type: 'string',
description: 'Updated translated content in HTML'
},
description: {
type: 'string',
description: 'Updated translated description'
},
state: {
type: 'string',
enum: ['draft', 'published'],
description: 'Updated translation state'
}
}
}
}
},
required: ['id']
}
}
]
};
});
/**
* 註冊工具呼叫處理
*/
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
if (name === 'get_article') {
const { id } = args as { id: string };
if (!id) {
throw new Error('Article ID is required');
}
const article = await callIntercomAPI(`/articles/${id}`);
return {
content: [{
type: 'text',
text: JSON.stringify(article, null, 2)
}]
};
}
if (name === 'list_articles') {
const { page = 1, per_page = 10 } = args as {
page?: number;
per_page?: number;
};
// 確保參數在合理範圍內
const validPage = Math.max(1, Math.floor(page));
const validPerPage = Math.min(50, Math.max(1, Math.floor(per_page)));
const data: ListArticlesResponse = await callIntercomAPI(
`/articles?page=${validPage}&per_page=${validPerPage}`
);
return {
content: [{
type: 'text',
text: JSON.stringify(data, null, 2)
}]
};
}
if (name === 'create_article') {
const { title, body, author_id, description, state, parent_id, parent_type, translated_content } = args as {
title: string;
body: string;
author_id: number;
description?: string;
state?: 'draft' | 'published';
parent_id?: string;
parent_type?: 'collection';
translated_content?: {
[locale: string]: {
title: string;
body: string;
description?: string;
author_id: number;
state?: 'draft' | 'published';
}
}
};
// 驗證必填欄位
if (!title || !body || !author_id) {
throw new Error('title, body, and author_id are required fields');
}
// 建構 request payload
const payload: any = {
title,
body,
author_id
};
if (description) payload.description = description;
if (state) payload.state = state;
if (parent_id) payload.parent_id = parent_id;
if (parent_type) payload.parent_type = parent_type;
if (translated_content) payload.translated_content = translated_content;
const article = await callIntercomAPI('/articles', 'POST', payload);
return {
content: [{
type: 'text',
text: JSON.stringify(article, null, 2)
}]
};
}
if (name === 'update_article') {
const { id, title, body, description, state, author_id, translated_content } = args as {
id: string;
title?: string;
body?: string;
description?: string;
state?: 'draft' | 'published';
author_id?: number;
translated_content?: {
[locale: string]: {
title?: string;
body?: string;
description?: string;
state?: 'draft' | 'published';
}
}
};
// 驗證必填欄位
if (!id) {
throw new Error('Article ID is required');
}
// 建構 update payload(只包含提供的欄位)
const payload: any = {};
if (title) payload.title = title;
if (body) payload.body = body;
if (description) payload.description = description;
if (state) payload.state = state;
if (author_id) payload.author_id = author_id;
if (translated_content) payload.translated_content = translated_content;
// 確保至少有一個欄位要更新
if (Object.keys(payload).length === 0) {
throw new Error('At least one field must be provided for update');
}
const article = await callIntercomAPI(`/articles/${id}`, 'PUT', payload);
return {
content: [{
type: 'text',
text: JSON.stringify(article, null, 2)
}]
};
}
throw new Error(`Unknown tool: ${name}`);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [{
type: 'text',
text: `Error: ${errorMessage}`
}],
isError: true
};
}
});
/**
* 主函數
*/
async function main() {
// 檢查環境變數
if (!INTERCOM_TOKEN) {
console.error('Error: INTERCOM_ACCESS_TOKEN environment variable is required');
console.error('Please set it in your MCP configuration or environment');
process.exit(1);
}
const transport = new StdioServerTransport();
await server.connect(transport);
// 使用 stderr 輸出(stdio 協定使用 stdout)
console.error('Intercom Articles MCP Server v0.2.0');
console.error('Running on stdio transport');
console.error('Tools available: get_article, list_articles, create_article, update_article');
}
// 啟動伺服器
main().catch((error) => {
console.error('Fatal error in main():', error);
process.exit(1);
});